序章

MERNスタックは、MongoDB、Express、React / Redux、およびNode.jsで構成されています。 MERNスタックは、最新のシングルページWebアプリケーションを構築するための最も人気のあるJavaScriptスタックの1つです。

このチュートリアルでは、 RESTfulAPIを使用するtodoアプリケーションをビルドします。これもこのチュートリアルの後半でビルドします。

Animated screenshot of a todo application

前提条件

このチュートリアルを完了するには、次のものが必要です。

注:このチュートリアルは、もともとmLabサービスを使用してMongoDBデータベースをホストするために作成されました。 mLabは、2019年2月以降、新しいアカウントの作成が禁止されており、MongoDBAtlasの使用を提案しています。

インストール手順に従ってMongoDBをローカルで実行することもできますが、このチュートリアルではそのプロセスについては説明せず、教育目的でそのまま使用します。

また、使い慣れたコードエディタ、できればJavaScriptコードの強調表示をサポートするコードエディタも必要です。

APIエンドポイントのテストには、Postmanなどのツールをダウンロードしてインストールすることをお勧めします。

このチュートリアルは、ノードv14.2.0、npm v6.14.5、mongodb-community v4.2.6、express v4.17.1、およびmongoosev5.9.17で検証されました。 。

ステップ1—アプリケーションのセットアップ

セットアップから始めましょう。 ターミナルを開き、ローカルマシンの任意の便利な場所に新しいファイルディレクトリを作成します。 名前は何でもかまいませんが、この例ではmern-todoと呼ばれています。

  1. mkdir mern-todo

次に、そのファイルディレクトリに入ります。

  1. cd mern-todo

次のステップは、package.jsonファイルでプロジェクトを初期化することです。 このファイルには、アプリと実行する必要のある依存関係に関する情報が含まれています。

次を使用できます。

  1. npm init

プロンプトが表示されたら、指示に従います。 または、次を使用できます。

  1. npm init -y

デフォルト値を使用します。

ステップ2—ノードサーバーのセットアップ

バックエンドでJavaScriptコードを実行するには、コードをコンパイルするサーバーを起動する必要があります。

サーバーは2つの方法で作成できます。1つは、ノードに組み込まれているhttpモジュールを使用する方法です。 2つ目は、Express.jsフレームワークを利用することです。

このチュートリアルでは、Express.jsを使用します。 これはNode.jsHTTPフレームワークであり、箱から出してすぐに多くのことを処理し、完全に機能するRESTfulAPIを作成するためのコードをほとんど必要としません。 Expressを使用するには、npmを使用してインストールします。

  1. npm install express

次に、ファイルindex.jsを作成し、次のコードを入力して保存します。

index.js
const express = require('express');

const app = express();

const port = process.env.PORT || 5000;

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

app.use((req, res, next) => {
  res.send('Welcome to Express');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

前のコードのこのスニペットは、開発およびテスト中にさまざまなドメインからAPIにアクセスしようとしたときに直面する可能性のあるCORS関連の問題を処理するのに役立ちます。

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

サーバーを起動して、サーバーが機能するかどうかを確認します。 index.jsファイルと同じディレクトリで端末を開き、次のように入力します。

  1. node index.js

すべてがうまくいけば、端末のポート5000サーバーが実行されていることがわかります。

ステップ3—ルートを作成する

アプリが実行する必要があることは3つあります。

  • タスクを作成する
  • すべてのタスクを表示
  • 完了したタスクを削除する

タスクごとに、todoアプリが依存する複数のエンドポイントを定義するルートを作成する必要があります。 それでは、フォルダroutesを作成し、次のコードを含むファイルapi.jsを作成しましょう。

  1. mkdir routes

api.jsを編集して、次のコードを貼り付けます。

ルート/api.js
const express = require('express');
const router = express.Router();

router.get('/todos', (req, res, next) => {
  // get placeholder
});

router.post('/todos', (req, res, next) => {
  // post placeholder
});

router.delete('/todos/:id', (req, res, next) => {
  // delete placeholder
});

module.exports = router;

これにより、GET、POST、およびDELETEのプレースホルダールートが提供されます。

ステップ4—モデルの定義

さて、興味深い部分があります。 アプリはNoSQLデータベースであるMongoDBを利用するため、モデルスキーマを作成する必要があります。 モデルは、スキーマインターフェイスを使用して定義されます。 スキーマを使用すると、検証要件とデフォルト値とともに、各ドキュメントに格納されているフィールドを定義できます。 本質的に、スキーマはデータベースがどのように構築されるかの青写真です。 さらに、静的メソッドとインスタンスヘルパーメソッドを定義して、データ型の操作を容易にしたり、他のフィールドと同じように使用できるがデータベースには保存されない仮想プロパティを定義したりできます。

スキーマとモデルを作成するには、MongoDBでの作業を容易にするノードパッケージであるMongooseをインストールします。

  1. # ensure that you are in the `mern-todo` project directory
  2. npm install mongoose

ルートディレクトリに新しいフォルダを作成し、modelsという名前を付けます。 その中にファイルを作成し、次のコードを含むtodo.jsという名前を付けます。

  1. mkdir models

テキストエディタを使用して、以下をtodo.jsに貼り付けます。

models / todo.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Create schema for todo
const TodoSchema = new Schema({
  action: {
    type: String,
    required: [true, 'The todo text field is required'],
  },
});

// Create model for todo
const Todo = mongoose.model('todo', TodoSchema);

module.exports = Todo;

次に、新しいモデルを使用するためにルートを更新する必要があります。

ルート/api.js
const express = require('express');
const router = express.Router();
const Todo = require('../models/todo');

router.get('/todos', (req, res, next) => {
  // This will return all the data, exposing only the id and action field to the client
  Todo.find({}, 'action')
    .then((data) => res.json(data))
    .catch(next);
});

router.post('/todos', (req, res, next) => {
  if (req.body.action) {
    Todo.create(req.body)
      .then((data) => res.json(data))
      .catch(next);
  } else {
    res.json({
      error: 'The input field is empty',
    });
  }
});

router.delete('/todos/:id', (req, res, next) => {
  Todo.findOneAndDelete({ _id: req.params.id })
    .then((data) => res.json(data))
    .catch(next);
});

module.exports = router;

ステップ5—データベースへの接続

データを保存するデータベースが必要になります。 このために、mLabを利用します。 ドキュメントに従って、mLabの使用を開始してください。

データベースを設定した後、index.jsファイルを次のコードで更新する必要があります。

index.js
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const routes = require('./routes/api');
require('dotenv').config();

const app = express();

const port = process.env.PORT || 5000;

// Connect to the database
mongoose
  .connect(process.env.DB, { useNewUrlParser: true })
  .then(() => console.log(`Database connected successfully`))
  .catch((err) => console.log(err));

// Since mongoose's Promise is deprecated, we override it with Node's Promise
mongoose.Promise = global.Promise;

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

app.use(bodyParser.json());

app.use('/api', routes);

app.use((err, req, res, next) => {
  console.log(err);
  next();
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

注: Express 4.16以降のバージョンでは、body-parserなどのミドルウェアに依存する必要がありました。 ただし、組み込みのパーサーを使用できるようになりました。

古いバージョンのExpressを使用している場合は、npmを使用してbody-parserをインストールします。

  1. npm install body-parser

前のコードでは、process.envを使用して、作成する必要のある環境変数にアクセスしました。 ルートディレクトリに.envという名前のファイルを作成し、次のように編集します。

.env
DB = 'mongodb://<USER>:<PASSWORD>@example.mlab.com:port/todo'

データベースとユーザーを作成した後は、mLabから独自のMongoDBURLを使用してください。 <USER>をユーザー名に置き換え、<PASSWORD>を作成したユーザーのパスワードに置き換えます。

環境変数を操作するには、dotenvというノードパッケージをインストールする必要があります。これにより、.envファイルに保存されている環境変数にアクセスできるようになります。

  1. # ensure that you are in the `mern-todo` project directory
  2. npm install dotenv

次に、index.jsで要求して構成します。

require('dotenv').config()

アプリケーションコードにクレデンシャルを直接書き込む代わりに環境変数を使用すると、バージョニングシステムから機密情報を隠すことができます。 この方法で、構成データとシークレットデータをアプリケーションコードから分離することがベストプラクティスと見なされます。

ステップ6—APIをテストする

これは、RESTfulAPIが機能していることを確認するために私たちが試し始める部分です。 フロントエンドはまだ準備ができていないため、一部のAPI開発クライアントを使用してコードをテストできます。

APIのテストには、PostmanまたはInsomnia、またはお好みのクライアントを使用できます。

次のコマンドを使用してサーバーを起動します。

  1. node index.js

次に、クライアントを開き、GETメソッドを作成して、http://localhost:5000/api/todosに移動します。

A screenshot of the Insomnia REST client testing endpoints

すべてのAPIエンドポイントをテストし、それらが機能していることを確認します。 bodyを必要とするエンドポイントの場合、コードで設定したものであるため、必要なフィールドを含むJSONを返送します。

POSTリクエストのサンプル:

POST localhost:5000/api/todos
Body
raw

サンプルPOST値:

{
  "action": "build a mern stack application"
}

GETリクエストのサンプル:

GET localhost:5000/api/todos

GET応答の例:

Output
[ { "id": "5bd4edfc89d4c3228e1bbe0a", "action": "build a mern stack application" } ]

サンプルのDELETEリクエスト:

DELETE localhost:5000/api/todos/5bd4edfc89d4c3228e1bbe0ad

GET、POST、およびDELETEの結果をテストして観察します。

ステップ7—フロントエンドを作成する

APIに必要な機能が完了したので、クライアントがAPIと対話するためのインターフェイスを作成します。 ToDoアプリのフロントエンドから始めるには、create-react-appコマンドを使用してアプリのスキャフォールディングを行います。

バックエンドコードと同じルートディレクトリであるmern-todoディレクトリで、次のコマンドを実行します。

  1. npx create-react-app client

これにより、mern-todoディレクトリにclientという新しいフォルダが作成され、そこにすべてのReactコードが追加されます。

ステップ8—Reactアプリを実行する

Reactアプリをテストする前に、プロジェクトのルートディレクトリにインストールする必要のある多くの依存関係があります。

まず、開発依存関係として同時にをインストールします。

  1. npm install concurrently --save-dev

同時に、同じターミナルウィンドウから複数のコマンドを同時に実行するために使用されます。

次に、nodemonを開発依存関係としてインストールします。

  1. npm install nodemon --save-dev

Nodemonは、サーバーの実行と監視にも使用されます。 サーバーコードに変更があった場合、Nodemonは新しい変更で自動的に再起動します。

次に、アプリプロジェクトのルートフォルダーにあるpackage.jsonファイルを開き、次のコードを貼り付けます。

package.json
{
  // ...
  "scripts": {
    "start": "node index.js",
    "start-watch": "nodemon index.js",
    "dev": "concurrently \"npm run start-watch\" \"cd client && npm start\""
  },
  // ...
}

クライアントフォルダに入り、package.jsonファイルを見つけて、その中に次のキーと値のペアを追加します。

client / package.json
{
  // ...
  "proxy": "http://localhost:5000"
}

package.jsonファイルのこのプロキシ設定により、完全なURLを入力しなくてもAPI呼び出しを行うことができ、/api/todosだけですべてのタスクを取得できます。

ターミナルを開いてnpm run devを実行し、clientディレクトリではなくtodoディレクトリにいることを確認します。

アプリはlocalhost:3000で開いて実行されます。

ステップ9—Reactコンポーネントの作成

Reactの利点の1つは、再利用可能でコードをモジュール化できるコンポーネントを利用することです。 ToDoアプリには、2つのステートコンポーネントと1つのステートレスコンポーネントがあります。

srcフォルダー内に、componentsという別のフォルダーを作成し、その中に3つのファイルInput.jsListTodo.js、およびTodo.jsを作成します。

Input.jsファイルを開き、以下を貼り付けます。

client / src / components / Input.js
import React, { Component } from 'react';
import axios from 'axios';

class Input extends Component {
  state = {
    action: '',
  };

  addTodo = () => {
    const task = { action: this.state.action };

    if (task.action && task.action.length > 0) {
      axios
        .post('/api/todos', task)
        .then((res) => {
          if (res.data) {
            this.props.getTodos();
            this.setState({ action: '' });
          }
        })
        .catch((err) => console.log(err));
    } else {
      console.log('input field required');
    }
  };

  handleChange = (e) => {
    this.setState({
      action: e.target.value,
    });
  };

  render() {
    let { action } = this.state;
    return (
      <div>
        <input type="text" onChange={this.handleChange} value={action} />
        <button onClick={this.addTodo}>add todo</button>
      </div>
    );
  }
}

export default Input;

ブラウザとNode.js用のPromiseベースのHTTPクライアントであるaxiosを利用するには、ターミナルからclientディレクトリに移動する必要があります。

  1. cd client

そして、npm install axiosを実行します。

  1. npm install axios

その後、ListTodo.jsファイルを開き、次のコードを貼り付けます。

client / src / components / ListTodo.js
import React from 'react';

const ListTodo = ({ todos, deleteTodo }) => {
  return (
    <ul>
      {todos && todos.length > 0 ? (
        todos.map((todo) => {
          return (
            <li key={todo._id} onClick={() => deleteTodo(todo._id)}>
              {todo.action}
            </li>
          );
        })
      ) : (
        <li>No todo(s) left</li>
      )}
    </ul>
  );
};

export default ListTodo;

次に、Todo.jsファイルに次のコードを記述します。

client / src / components / Todo.js
import React, { Component } from 'react';
import axios from 'axios';
import Input from './Input';
import ListTodo from './ListTodo';

class Todo extends Component {
  state = {
    todos: [],
  };

  componentDidMount() {
    this.getTodos();
  }

  getTodos = () => {
    axios
      .get('/api/todos')
      .then((res) => {
        if (res.data) {
          this.setState({
            todos: res.data,
          });
        }
      })
      .catch((err) => console.log(err));
  };

  deleteTodo = (id) => {
    axios
      .delete(`/api/todos/${id}`)
      .then((res) => {
        if (res.data) {
          this.getTodos();
        }
      })
      .catch((err) => console.log(err));
  };

  render() {
    let { todos } = this.state;

    return (
      <div>
        <h1>My Todo(s)</h1>
        <Input getTodos={this.getTodos} />
        <ListTodo todos={todos} deleteTodo={this.deleteTodo} />
      </div>
    );
  }
}

export default Todo;

Reactコードを少し調整する必要があります。 ロゴを削除し、App.jsを次のように調整します。

client / src / App.js
import React from 'react';
import Todo from './components/Todo';
import './App.css';

const App = () => {
  return (
    <div className="App">
      <Todo />
    </div>
  );
};

export default App;

次に、次のコードをApp.cssに貼り付けます。

client / src / App.css
.App {
  text-align: center;
  font-size: calc(10px + 2vmin);
  width: 60%;
  margin-left: auto;
  margin-right: auto;
}

input {
  height: 40px;
  width: 50%;
  border: none;
  border-bottom: 2px #101113 solid;
  background: none;
  font-size: 1.5rem;
  color: #787a80;
}

input:focus {
  outline: none;
}

button {
  width: 25%;
  height: 45px;
  border: none;
  margin-left: 10px;
  font-size: 25px;
  background: #101113;
  border-radius: 5px;
  color: #787a80;
  cursor: pointer;
}

button:focus {
  outline: none;
}

ul {
  list-style: none;
  text-align: left;
  padding: 15px;
  background: #171a1f;
  border-radius: 5px;
}

li {
  padding: 15px;
  font-size: 1.5rem;
  margin-bottom: 15px;
  background: #282c34;
  border-radius: 5px;
  overflow-wrap: break-word;
  cursor: pointer;
}

@media only screen and (min-width: 300px) {
  .App {
    width: 80%;
  }

  input {
    width: 100%
  }

  button {
    width: 100%;
    margin-top: 15px;
    margin-left: 0;
  }
}

@media only screen and (min-width: 640px) {
  .App {
    width: 60%;
  }

  input {
    width: 50%;
  }

  button {
    width: 30%;
    margin-left: 10px;
    margin-top: 0;
  }
}

また、index.cssに次のルールを追加します。

client / src / index.css
body {
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  box-sizing: border-box;
  background-color: #282c34;
  color: #787a80;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}

これらすべてのファイルを保存するときにエラーがないと仮定すると、todoアプリは準備ができており、前述の機能(タスクの作成、タスクの削除、すべてのタスクの表示)で完全に機能します。

結論

このチュートリアルでは、MERNスタックを使用してtodoアプリを作成しました。 Express.jsを使用して作成されたバックエンドアプリケーションと通信するReactを使用してフロントエンドアプリケーションを作成しました。 また、データベースにタスクを保存するためのMongoDBバックエンドを作成しました。