#事前知識
##APIサーバーとは
Webサービスのバックエンドによく使われる。
「PHP入門」などでWebサービスを作るシーンが多く見られますがその違いは簡単に言うと「Webサービスの外部から利用できるかできないか」です。
多くのPHP入門の記事ではPHPでMySQLなどのデータベースからデータを取ってきてそれをそのままecho
などで表示する例がよく見られます。
しかし最近のWebサービス開発ではフロントエンドの役割をする部分がバックエンドのAPIサーバーにリクエストを投げてAPIサーバーがデータベースからデータを取ってきてそれをフロントエンドに返しページに表示するのが多い気がします。
APIサーバーを立てるメリットとして僕が一番思うのはそのアプリのスマホ版を作る時に楽です。
主なAPIサーバーの機能をまとめると「 受け取ったリクエストに対してそれに応じたデータベース操作をして結果を返してあげる 」です。
##jsonAPIサーバーとは
結果をjson形式で返すAPIサーバーです。
他にはXMLで返すapiサーバーもあります。
#環境
バージョンは一桁目以外は気にしないで大丈夫です。
Expressは後に説明するので他の4つを揃えといてください。
##Node.js
v5.7.1
サーバーサイドでJavaScript動かすやつ
##Express
4.13.1
Node.jsの上で動くMVCフレームワーク
jsonAPIサーバーとしての役割以外にejs
というテンプレートエンジンを用いたWeb開発もできます
##MongoDB
3.2.3
ドキュメント指向のデータベース
##npm
3.10.7
Node.jsのライブラリやパッケージを管理するパッケージマネージャ
##postman
httpリクエストを送ってAPIのテストを行うことができる
#データベースの構成
今回はWebサービスによくある「投稿記事(article)」と「ユーザー(user)」のデータを扱うのでデータベースは以下のようです
Node.jsでMongoDBを利用して読み書きを行う際、この一つのデータの塊をモデルとして扱います
##投稿記事(article)
カラム名 | 型 | 説明 |
---|---|---|
title | String | 記事タイトル |
text | String | 記事の本文 |
date | String | 投稿日時 |
_id | ObjectId ※ | 投稿記事固有のID |
##ユーザー(user)
カラム名 | 型 | 説明 |
---|---|---|
name | String | ユーザーの名前 |
screen_name | String | Twitterの @~~ みたいな |
bio | String | 軽い自己紹介 |
_id | ObjectId ※ | ユーザー固有のID |
※ ObjectIdとは = mongodbが他と被らないように振ってくれるIDです
#練習用に作ってみるAPI
method | url | 説明 |
---|---|---|
GET | /api/v1/article | 記事一覧を取得 |
GET | /api/v1/article/:id | :idで指定したの記事を取得 |
POST | /api/v1/article | 記事投稿 |
DELETE | /api/v1/article/:id | :idで指定した記事を削除 |
GET | /api/v1/user:/ | ユーザー一覧を取得 |
GET | /api/v1/user:/id | :idで指定したユーザー情報取得 |
POST | /api/v1/user/ | ユーザーの新規追加 |
PUT | /api/v1/user/:id | :idで指定したユーザー情報更新 |
ちょっと多いですが頑張りましょう
#準備
npm init
$ mkdir express-api
$ cd express-api
$ npm init
今回はexpress-api
をプロジェクトのディレクトリとします。
作ったディレクトリの中でnpm init
を実行してください。いくつか質問が出てきますが全て空欄でエンターを押して大丈夫です。
ディレクトリ構成
とりあえずこういう感じで作ってください
express-api
|---- /app
| └ app.js
| └ /models
|ㅤ └ /route
| └ v1
|---- package.json
app.js これを実行してサーバーを起動します
/route このディレクトリは各apiのコードを書いたjsファイル入れていきます
/models データベースのモデル情報を書いたjsファイルを入れていきます
package.js npm init
によって生産されたものです
必要なライブラリなどのインストール
$ npm install express --save
$ npm install mongodb --save
$ npm install mongoose --save
$ npm install body-parser --save
$ npm install moment --save
npmを使ってインストールしていきます。
末尾に--save
をつけるとpackage.jsonに書き込んでくれます。
package.jsonに書き込むと他人のPCにこのプロジェクトを送った時にnpm install
と打つだけでpackage.jsonに記載されたものが全てインストールされます。便利ですね!!
コードを書く
Hello,World
まずは先ほど書いたapiではなくテスト用に以下のようなapiを作ります
method | url | 説明 |
---|---|---|
GET | /api/v1/ | 「Hello,world」とjsonが返ってきます |
// ライブラリ読み込み
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
//body-parserの設定
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var port = process.env.PORT || 3000; // port番号を指定
// GET http://localhost:3000/api/v1/
app.get('/api/v1/',function(req,res){
res.json({
message:"Hello,world"
});
});
//サーバ起動
app.listen(port);
console.log('listen on port ' + port);
実行してみましょう。
$ node app/app.js
listen on port 3000
サーバーはこれでポート番号3000に起動できました。
res.json()
で結果を返しています。
postmanを使ってhttp://localhost:3000/api/v1/
にGETリクエストを送りましょう(postmanの使い方は省略します)
以下のように返ってくれば成功です!!
{
"message": "Hello,world"
}
apiをディレクトリで分けて綺麗にする
まず、app.js
を以下のように書き換えます
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var port = process.env.PORT || 3000;
var router = require('./routes/v1/');
app.use('/api/v1/', router);
//サーバ起動
app.listen(port);
console.log('listen on port ' + port);
新しく追加した二行で「/routes/v1/index.jsをrouterとして読み込むよー」という処理と「それを/api/v1/のapiとして使うよー」という処理をしてます。
app.jsの既存のapp.get()
部分は消してください。
ちなみに、/routes/v1/
は/routes/v1/index.js
として扱われます。ディレクトリを参照した場合、その下のindex.jsが読み込まれます。
次に、apiの処理をapp/routes/v1にindex.js
を作りましょう
var express = require('express');
// ルーティングするで
var router = express.Router();
// routerにルーティングの動作を書いてく
router.get('/',function(req,res){
res.json({
message:"Hello,world"
});
});
//routerをモジュールとして扱う準備
module.exports = router;
このコードをapp.js
から呼び出すのでrouter.get
の第一引数は /
で大丈夫です。app.js
で「それを/api/v1/のapiとして使うよー」と宣言してるのでこのapiはhttp://localhost:3000/api/v1/
として使えます。
サーバーを再起動してpostmanでapiを叩いても結果は先ほどと変わらずしっかり動くはずです
##さらに分ける
ここからついに本題です(`・ω・´)
article関係のapiとuser関係のapiをどっちもindex.js
に書くものアリです。
しかしそれだとapiが増えた時読みづらいのでroutes/v1/
の中にarticle.js
とuser.js
を作りapiを分けましょう。
試しに各種test用のapiを作ります
method | url | 説明 |
---|---|---|
GET | /api/v1/article/test | 「This is article api」と返す |
GET | /api/v1/user/test | 「This is user api」と返す |
そうなるとディレクトリ構成は以下のような感じです
express-api
|---- /app
| └ app.js
| └ /models
|ㅤ └ /route
| └ v1
| └ index.js
| └ article.js
| └ user.js
|---- package.json
それではindex.js
を以下のように書き換えましょう
var express = require('express');
var router = express.Router();
router.use('/article', require('./article.js'));
router.use('/user', require('./user.js'));
module.exports = router;
新たに追加された2行は「article.jsの中に書いてあるapiを/articleとして使うよー」と「user.jsの中に書いてあるapiを/userとして使うよー」という意味です。
つまりindex.js
から見れば「それを/api/v1/のapiとして使うよー」と言ってるので/article
として扱うということはapp.jsから見た時/api/v1/article/
として扱われることを意味します。
それでは中身を書いていきます
var express = require('express');
var router = express.Router();
// GET http://localhost:3000/api/v1/article/test
router.get('/test', function (req, res) {
res.json({
message:"This is article api"
});
});
//routerをモジュールとして扱う準備
module.exports = router;
var express = require('express');
var router = express.Router();
// GET http://localhost:3000/api/v1/user/test
router.get('/test', function (req, res) {
res.json({
message:"This is user api"
});
});
//routerをモジュールとして扱う準備
module.exports = router;
サーバーを再起動してpostmanでapiを叩いてみましょう
http://localhost:3000/api/v1/article/test
にリクエストを送った時
{
"message": "This is article api"
}
と結果が返ってくると思います。
http://localhost:3000/api/v1/user/test
も上手く結果が返ってくればバッチリです!!この2つのapiはテスト用なので消してしまっても構いません。
##DB操作を含むapiを作っていく
###DB起動
ターミナルをもう1つ開き以下のように実行してください。
$ sudo mongod --dbpath /var/db/mongo/
これでMongoDBが起動し、読み書きができる状態になりました
###Modelファイル作成
データベースの準備から始めます
データベースに入れるデータを書いたファイルを**/models**に作ります
それぞれarticleModel.js
とuserModel.js
としましょう
var mongoose = require('mongoose'); //mongoDBに接続するためのライブラリ
var Schema = mongoose.Schema; //mongoDBのスキーマを作る
var moment = require('moment');
var ArticleSchema = new Schema({
title :String,
text: String,
date: String
});
ArticleSchema.methods.setDate = function () {
//作った時間をセット
this.date = moment().format("YYYY-MM-DD HH:mm:ss");
};
// スキーマをモデルとしてコンパイルし、それをモジュールとして扱えるようにする
module.exports = mongoose.model('ArticleModel', ArticleSchema);
var mongoose = require('mongoose'); //mongoDBに接続するためのライブラリ
var Schema = mongoose.Schema; //mongoDBのスキーマを作る
var UserSchema = new Schema({
name :String,
screen_name: String,
bio: String
});
// スキーマをモデルとしてコンパイルし、それをモジュールとして扱えるようにする
module.exports = mongoose.model('UserModel', UserSchema);
簡単に流れを説明すると、mongoose
を読み込み、スキーマを作るのに必要なカラム名とデータ型を記述していき、最後の行でスキーマをモデルに変換してモジュールとして扱うようにしています。
ArticleModel.jsに見られるArticleSchema.methods.setDate
に代入した関数はNode.jsからそれぞれの値を入れるときに呼び出すための関数です。
この場合、ArticleSchemaのdateに現在時刻をセットしてます。
また、_idは自動生産されます。
app.js編集
まずapp.jsに以下を追記しましょう(4行目で大丈夫です)
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/ExpressAPI');
mongoose.connection.on('error', function(err) {
console.error('MongoDB connection error: ' + err);
process.exit(-1);
});
それではユーザーの新規追加APIを作りましょう
(本来ユーザー登録関係のapiにはtoken発行などが必要ですが今回は省略し、ユーザーの基本情報を新しく登録する処理のみとさせていただきます)
###POST /api/v1/user/
user.jsを以下のようにします
var express = require('express');
var router = express.Router();
var UserModel = require('../../models/userModel.js');
router.post('/',function(req,res){
// モデル作成.
var User = new UserModel();
// データを詰め込む
User.name = req.body.name;
User.screen_name = req.body.screen_name;
User.bio = req.body.bio;
// 保存処理
User.save(function(err) {
if (err){
// エラーがあった場合エラーメッセージを返す
res.send(err);
} else {
// エラーがなければ「Success!!」
res.json({ message: 'Success!!' });
}
});
});
//routerをモジュールとして扱う準備
module.exports = router;
POSTを受け取った時に実行したいのでrouter.post()
で書きます。
コメント文の通り、userModel.jsを使い、モデルを作成し、そこにデータを詰め込みUser.save()
でDBに保存しています。
req.body.~~
で一緒に送られてきたデータが取得してます。
サーバーを再起動してpostmanでPOSTリクエストを投げてみましょう。
今回はnameとscreen_nameとbioの値も一緒にリクエストしないといけないのでpostmanのBody欄でKeyとValueを指定してあげましょう
エラーが出なければ保存できてます👍
###GET /api/v1/user/
それでは保存したデータを取得するAPIを作ります
以下を追記してください。
router.get('/', function (req, res) {
UserModel
.find()
.then(function (users) {
res.json(users);
});
});
全て取得するのは超簡単!!
UserModel.find()
でUserModelが全て取得できます。
then()
で受け取った時の処理を書きます。ここはres.jsonでそのまま結果を返してあげれば完了です!!
[
{
"_id": "582c8fc9e3841640e1140ce4",
"bio": "Hello",
"name": "太郎",
"screen_name": "tarou04",
"__v": 0
},
{
"_id": "582c9144e3841640e1140ce5",
"bio": "Hello",
"name": "二郎",
"screen_name": "jirou08",
"__v": 0
}
]
postmanでGETリクエストを送ってこんな感じで返ってくれば成功です
###GET /api/v1/user/:id
次は末尾の:_id
によって返す値を変えるapiです
先ほどDBに登録した二郎君にmongoDBが582c9144e3841640e1140ce5
という素敵なidを振ってくれたのでこのidをリクエストすると二郎君の情報が取れる、といったapiを作りたいと思います!!
router.get('/:id', function (req, res) {
var Userid = req.params.id;
UserModel
.findById(Userid,function (err,user) {
res.json(user);
});
});
第一引数でurlを指定する際に:id
と書くとそれはreq.params.id
として受け取れます。
もちろん、:screen_name
と書けばreq.params.screen_name
です!
関数findById()
は引数と_idが一致したデータを返します
それではpostmanでリクエストします
今回僕はhttp://localhost:3000/api/v1/user/582c91a1db5998416ca5fc93
にGETリクエストを投げたので、以下のように返ってきました
{
"_id": "582c91a1db5998416ca5fc93",
"bio": "Hello",
"screen_name": "jirou08",
"name": "二郎",
"__v": 0
}
この場合findById()
の引数が582c91a1db5998416ca5fc93
になったのでそれに一致したユーザーである二郎くんの情報が返ってきました
###PUT /api/v1/user/:id
これはPUT
なので指定ユーザーの情報を更新する、他のものに変えるapiです。
router.put('/:id',function (req, res) {
var Userid = req.params.id;
UserModel
.findById(Userid, function(err, user) {
if (err) {
res.send(err);
} else {
user.name = req.body.name;
user.screen_name = req.body.screen_name;
user.bio = req.body.bio;
user.save(function(err) {
if (err){
res.send(err);
} else {
res.json({ message: 'Success!' });
}
});
}
});
});
簡単に言うとGET /api/v1/user/:id
とPOST /api/v1/user/
が混ざったような感じです。
findById()
で取得したデータをコールバック関数の中でuser
として扱いそこの中のデータを入れ替えてそのままsave()
しています。
今回はfindById()
で取得したデータを使ってるのでnew UserModel()
は不要です
今回もname、screen_name、bioを含めてリクエストを送ってみましょう
これでリクエストしたので二郎くんのbioを「Hello」から「buongiorno!!」に変わりました!!
###POST /api/v1/article
var express = require('express');
var router = express.Router();
var ArticleModel = require('../../models/articleModel.js');
router.post('/',function(req,res){
// モデル作成.
var Article = new ArticleModel();
// データを詰め込む
Article.title = req.body.title;
Article.text = req.body.text;
Article.setDate();
// 保存処理
Article.save(function(err) {
if (err){
// エラーがあった場合エラーメッセージを返す
res.send(err);
} else {
// エラーがなければ「Success!!」
res.json({ message: 'Success!!' });
}
});
});
//routerをモジュールとして扱う準備
module.exports = router;
基本的な動きはPOST /api/v1/user
と同じです。
唯一違う点としてデータを詰め込む際にsetDate()
という関数を呼び出してる点です。
これはスキーマを作成するときにarticleModel.jsに書いた関数をここで呼び出しています。
###GET /api/v1/article
router.get('/', function (req, res) {
ArticleModel
.find()
.then(function (articles) {
res.json(articles);
});
});
GET /api/v1/user
とほぼ同じなので説明は省略します
###GET /api/v1/article/:id
router.get('/:id', function (req, res) {
var Articleid = req.params.id;
ArticleModel
.findById(Articleid,function (err,article) {
res.json(article);
});
});
###DELETE /api/v1/article/:id
router.delete('/:id',function(req,res){
var Articleid = req.params.id;
ArticleModel.remove({_id:Articleid})
.then(function(){
res.json({message:'Success!!'});
});
});
こちらもとても簡単!
ArticleModel.remove({_id:Articleid})
でArticleModelの中から_id
が一致したものを削除しています
最終的なディレクトリ構成とコード
ディレクトリ
express-api
|---- /app
| └ app.js
| └ /models
| └ articleModel.js
| └ userModel.js
|ㅤ └ /route
| └ v1
| └ index.js
| └ article.js
| └ user.js
|---- package.json
|---- /node_modules
###コード
複数回の編集があったもののみです
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/ExpressAPI');
mongoose.connection.on('error', function(err) {
console.error('MongoDB connection error: ' + err);
process.exit(-1);
});
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var port = process.env.PORT || 3000;
var router = require('./routes/v1/');
app.use('/api/v1/', router);
//サーバ起動
app.listen(port);
console.log('listen on port ' + port);
var express = require('express');
var router = express.Router();
var ArticleModel = require('../../models/articleModel.js');
router.post('/',function(req,res){
// モデル作成.
var Article = new ArticleModel();
// データを詰め込む
Article.title = req.body.title;
Article.text = req.body.text;
Article.setDate();
// 保存処理
Article.save(function(err) {
if (err){
// エラーがあった場合エラーメッセージを返す
res.send(err);
} else {
// エラーがなければ「Success!!」
res.json({message: 'Success!!'});
}
});
});
router.get('/', function (req, res) {
ArticleModel
.find()
.then(function (articles) {
res.json(articles);
});
});
router.get('/:id', function (req, res) {
var Articleid = req.params.id;
ArticleModel
.findById(Articleid,function (err,article) {
res.json(article);
});
});
router.delete('/:id',function(req,res){
var Articleid = req.params.id;
ArticleModel.remove({_id:Articleid})
.then(function(){
res.json({message:'Success!!'});
});
});
//routerをモジュールとして扱う準備
module.exports = router;
var express = require('express');
var router = express.Router();
var UserModel = require('../../models/userModel.js');
router.get('/test', function (req, res) {
res.json({
message:"This is user api"
});
});
router.post('/',function(req,res){
// 新しいユーザのモデルを作成する.
var User = new UserModel();
// ユーザの各カラムの情報を取得する.
User.name = req.body.name;
User.screen_name = req.body.screen_name;
User.bio = req.body.bio;
// ユーザ情報をセーブする.
User.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Success!' });
});
});
router.get('/', function (req, res) {
UserModel
.find()
.then(function (users) {
res.json(users);
});
});
router.get('/:id', function (req, res) {
var Userid = req.params.id;
UserModel
.findById(Userid,function (err,user) {
res.json(user);
});
});
router.put('/:id',function (req, res) {
var Userid = req.params.id;
UserModel
.findById(Userid, function(err, user) {
if (err) {
res.send(err);
} else {
user.name = req.body.name;
user.screen_name = req.body.screen_name;
user.bio = req.body.bio;
user.save(function(err) {
if (err){
res.send(err);
} else {
res.json({ message: 'Success!' });
}
});
}
});
});
//routerをモジュールとして扱う準備
module.exports = router;
#あとがき
v1って何
version1です。アプリに新機能を追加するときはv2
などのディレクトリ名を作ってapiを書いていくと良いと思います👍
##最後に
誤字など誤ってる部分があったら教えてください。
また、わからない部分があれば@leafia78にメンション飛ばして頂ければ出来る範囲で答えます