3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Dockerで簡易版Aidemyをつくってみよう (その3) ~Expressによるバックエンド~

Last updated at Posted at 2018-12-17

概要

Aidemy アドベントカレンダー17日目担当の @km42428 です。
今回は、Dockerで簡易版Aidemyをつくってみようシリーズのその3です。
前回はフロントエンドを作成したので、今回はバックエンドを作成します。

ちなみに、前回までのリンクは以下です。

Dockerで簡易版Aidemyをつくってみよう (その1) ~全体構成とDockerのセットアップ~
https://qiita.com/km42428/items/4e4653aa0e813282986b

Dockerで簡易版Aidemyをつくってみよう (その2) ~React.jsによるフロントエンド~
https://qiita.com/km42428/items/989dac5e7450501c452d

今回の目標

今回はエクササイズデータを渡すAPIを作成し、その2で作成したWebページから呼び出して値を表示して見ましょう。
そして、前回同様 docker-compose コマンドを利用し、フロントエンド・バックエンドを一発で起動できるようにするのが目標です。
バックエンドはNode.jsのバックエンドライブラリである Express を使います。
ソースは以下に公開しています。
https://github.com/km42428/aidemy-simple-pack/tree/backend

今回の記事は backend ブランチで動作しています。
masterブランチは適宜更新しているので、この記事と同じ動作をさせたい場合は、
以下のコマンドを入力してください。
自分の作業ブランチは適宜書き換えてください。

# クローン
$ git clone https://github.com/km42428/aidemy-simple-pack.git 

# リモートのfrontendブランチにチェックアウト
$ git fetch && git checkout origin/backend

# ローカルの自分の作業ブランチにチェックアウト
$ git checkout -b {自分の作業ブランチ} 

APIの構成

APIは以下のような構成を考えます。

メソッド パス 内容
GET / 生存確認 ({}を返すのみ)
GET /exercises エクササイズ全てを取得する
GET /exercises/:exerciseId 特定の一つのエクササイズを取得する

事前準備

バックエンドの挙動をローカルで確認するには、
nodenpm を事前にインストールする必要があります。
インストール手順については、前の記事にまとめているのでそちらをご覧ください。
https://qiita.com/km42428/items/989dac5e7450501c452d#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99

※ 今回の最終目的もDockerを使って環境開発不要で起動することです。
なので、環境開発なんてしたくないやいっ!という方は、
ソースをcloneした上で、Dockerによる操作からお読みください。

ローカルのでの開発

バックエンドフォルダを作成・移動

前回記事のフロントエンドと合わせて挙動を確認したい場合は、
前回のaidemy-frontend フォルダと同じ階層で作業してください。
バックエンドのフォルダ名は aidemy-backend とします。

mkdir aidemy-backend && cd $_

後ろの cd $_ で作成したフォルダに移動できます。

npm の有効化を行う

以下のコマンドで npm コマンドの初期化を行います。

npm init

以下のような設問が出てきますが、全てEnterでOKです

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (aidemy-backend)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/XXXXXXXX/aidemy-simple-pack/aidemy-backend/package.json:

{
  "name": "aidemy-backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) 

実行が終わると、
package.jsonが生成されています

.gitignore の作成

npm init が終わると、 node_modules というフォルダが作成されます。
こちらはnpmで用いるモジュール等が大量に入っています。
モジュールの情報は package.jsonのdependencies にも入っていて、
もし他の人とソースをgitで共有する場合、 npm install コマンドを入力すると同様の node_modules が作成されます。
なので、このフォルダ自体は共有不要なのでgitで履歴に反映されないように .gitignore ファイルを作成します。

aidemy-backend 直下に.gitignoreを作成します

touch .gitignore

.gitignoreを編集します。

node_modules/

必要なモジュールのインストール

npm install express body-parser

APIの作成

aidemy-backend 直下にindex.jsを作成します

touch index.js

実際にAPIを記述していきましょう。

// 必要なパッケージの読み込み
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  res.header(
    "Access-Control-Allow-Methods",
    "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"
  );
  next();
});

// expressでAPIサーバを使うための準備
var router = express.Router();

// エクササイズマスターデータ
const exercises = [
  {
    exerciseId: "exercise1",
    title: "1. Hello world",
    script: "# Hello worldを出力しましょう\n",
    answer: "# Hello worldを出力しましょう\nprint('Hello world')"
  },
  {
    exerciseId: "exercise2",
    title: "2. コメントの入力",
    script: "# 3 + 5 の結果を出力しましょう\n",
    answer: "# 3 + 5 の結果を出力しましょう\nprint(3 + 5)"
  }
];

/**
 * 特定のエクササイズを抽出する関数
 * @param exerciseId - エクササイズID
 * */
function getExercise(exerciseId) {
  return exercises.find(el => {
    return el.exerciseId === exerciseId;
  });
}

/* -------- 以下にAPIを記述 -------- */

router.route("/").get(async function(_req, res) {
  res.json({});
});
router.route("/exercises").get(async function(_req, res) {
  if (!exercises) {
    res.status(404).json({
      code: 404,
      msg: "Exercises not found"
    });
  }
  res.json(exercises);
});
router.route("/exercises/:exerciseId").get(async function(req, res) {
  console.log(req.params);
  const exercise = getExercise(req.params.exerciseId);
  if (!exercise) {
    res.status(404).json({
      code: 404,
      msg: "Exercise not found"
    });
  }
  res.json(exercise);
});

/* -------- 以上にAPIを記述 -------- */

// ルーティング登録
app.use("/v1", router);

// port4000番で出力を受ける
app.listen(4000);


package.jsonの編集

package.jsonを1行加えます。


{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "serve": "node index.js"
  }
}

動作確認

起動

以下のコマンドで起動します。

npm run serve

出力確認

こちらに、動作確認に便利な Postman についてまとめられた記事を紹介します。
https://qiita.com/zaburo/items/16ac4189d0d1c35e26d1
かなり便利なので入れておきましょう。

以下の画像のように、GETメソッドで右の欄にURLを書いてSendを押せば、APIの動作確認ができます。
スクリーンショット 2018-12-17 11.54.58.png

GETメソッドであれば、ブラウザのURL欄に貼り付けても動きます。

URL

http://localhost:4000/v1/

出力


{}

URL

http://localhost:4000/v1/exercises

出力


[
  {
    "exerciseId": "exercise1",
    "title": "1. Hello world",
    "script": "# Hello worldを出力しましょう\n",
    "answer": "# Hello worldを出力しましょう\nprint('Hello world')"
  },
  {
    "exerciseId": "exercise2",
    "title": "2. コメントの入力",
    "script": "# 3 + 5 の結果を出力しましょう\n",
    "answer": "# 3 + 5 の結果を出力しましょう\nprint(3 + 5)"
  }
]

URL

http://localhost:4000/v1/exercises/exercise1

出力



{
  "exerciseId": "exercise1",
  "title": "1. Hello world",
  "script": "# Hello worldを出力しましょう\n",
  "answer": "# Hello worldを出力しましょう\nprint('Hello world')"
}

URL

http://localhost:4000/v1/exercises/wrongid

出力



{
  "code": 404,
  "msg": "Exercise not found"
}

フロントエンドの設定

前回の記事ではエクササイズ情報を aidemy-frontend内に記述していました。
これを、今作成した aidemy-backend から参照するようにします。

必要なモジュールをinstallする

今回は axios というモジュールを使います。
axios はnode.jsでhttpリクエストを行うツールです。
公式ドキュメントは以下です。
https://github.com/axios/axios


npm install axios

axiosでAPIを叩く準備

axiosをimportし、apiのurlも変数に入れます。


  import React, { Component } from "react";
  import { BrowserRouter, Route, Link } from "react-router-dom";
  import AceEditor from "react-ace";
  import "brace/mode/python";
  import "brace/snippets/python";
  import "brace/theme/tomorrow";
+ import axios from "axios";

+ const apiUrl = "http://localhost:4000/v1";

フロントエンドのマスターデータをコメントアウト

バックエンドからエクササイズ情報を取得するので、フロントエンドの記述はコメントアウトします。
複数行コメントアウトは /* */ で囲めば大丈夫です。


/*
// マスターデータ
const exercises = [
  {
    exerciseId: "exercise1",
    title: "1. Hello world",
    script: "# Hello worldを出力しましょう\n"
  },
  {
    exerciseId: "exercise2",
    title: "2. コメントの入力",
    script: "# 3 + 5 の結果を出力しましょう\n"
  }
];

function getExercise(exerciseId) {
  return exercises.find(el => {
    return el.exerciseId === exerciseId;
  });
}
*/

エクササイズ情報をAPI取得する

axios を使って、API経由でエクササイズを取得します。


// exerciseの一覧
class Exercises extends Component {
  constructor() {
    super();
    this.state = {
      exercises: []
    };
  }

  // コンポーネントのレンダリングが終了したら呼び出される
- componentDidMount() {
-   this.setState({
-     exercises
-   });
- }
+ async componentDidMount() {
+   const exercises = (await axios.get(`${apiUrl}/exercises/`)).data;
+   // APIにアクセスできない場合に備えて条件分岐
+   if (!exercises) {
+     this.setState({
+       exercises: []
+     });
+     return;
+   }
+   this.setState({
+     exercises
+   });
+ }

  // 実際の表示部分
  render() {
    ...
  }
}

// 特定のexerciseの演習部分
class Exercise extends Component {
  constructor() {
    super();
    this.state = {
      exerciseId: "",
      title: "",
      script: ""
    };
    this.onChange = this.onChange.bind(this); // はじめにonChangeをthisにbindしておく
  }

  // コンポーネントのレンダリングが終了したら呼び出される
  async componentDidMount() {
-   const exercise = getExercise(this.props.match.params.id);
+   const exercise = (await axios.get(`${apiUrl}/exercises/${this.props.match.params.id}`)).data;
    if (exercise) {
      this.setState({
        exerciseId: exercise.exerciseId,
        title: exercise.title,
        script: exercise.script
      });
      return;
    }
    this.setState({
      exerciseId: "",
      title: "",
      script: ""
    });
  }
  ...

これでWEBページに値が表示されるはずです。

aidemy-frontend, aidemy-backend どちらも起動させて挙動を見てみてください。

Chromeを使っている場合は、右クリックで検証 > Network より、APIから値を取得しているのを確認できます。

Dockerによる操作

docker-compose で動作するために

aidemy-backendフォルダ内にDockerfileを配置

 ├ aidemy-backend/
 │ └ Dockerfile # 新規追加
 └ aidemy-frontend/
    └ Dockerfile

Dockerfileを記述


FROM node:8
WORKDIR /usr/src/app
COPY . .
RUN npm install
CMD npm run serve

aidemy-backendフォルダと同じ階層にdocker-compose.yml 配置

 ├ aidemy-backend/
 │ └ Dockerfile
 ├ aidemy-frontend/
 │ └ Dockerfile
 └ docker-compose.yml

docker-compose.ymlを記述



version: "3"
services:
  aidemy-frontend:
    build: ./aidemy-frontend
    image: aidemy-frontend
    environment:
      - ENV
    ports:
      - 5000:5000
  aidemy-backend:
    build: ./aidemy-backend
    image: aidemy-backend
    environment:
      - ENV
    ports:
      - 4000:4000

docker-composeによる起動

前回同様にdocker-composeで起動します

docker imageの作成

docker-compose build

フォアグラウンドでの起動

docker-compose up

http://localhost:8080 で動作すればOK

※バックグラウンドでの起動

docker-compose up -d

※バックグラウンドで起動したプロセスの終了

docker-compose down

今回は以上になります。
Dockerの使い方には慣れてきたでしょうか?
次回は12/19にデータベースを開発していきます!

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?