アプリケーションの構成および環境
前回、以下の記事でReact+Firebaseという構成で、CRUDアプリを作成しました。
今回は、この構成を
- フロントエンド:React
- バックエンド:Express
- データベース:MongoDB
として、CRUDアプリを作成してみます。
今回は以下の環境で開発しました。
- node: v14.15.3
- mongoDB: “4.4.3”
ディレクトリ構造
まず、全体のディレクトリを作成します。
mkdir react-express-mongo
cd react-express-mongo
Reactのソースコード
まず、create-react-appでreactアプリを作成します。
npx create-react-app react
そして、必要なライブラリをインストールします。
npm install react-router
次に、srcディレクトリを次のように変更します。
.
├── node_modules/
├── package-lock.json
├── package.json
├── public/
└── src
├── components/
├── constants/
├── format/
├── index.js
├── serviceWorker.js
└── setupTests.js
まず、index.jsを次のように編集します。
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
serviceWorker.unregister();
component/App.jsは以下のようにします。
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import * as ROUTES from "../constants/routes";
import Show from "./Show";
import Create from "./Create";
import Update from "./Update";
class App extends React.Component {
render() {
return (
<h1>App Component</h1>
);
}
}
export default App;
次に、Create, Read, UpdateおよびDeleteを行うルートを定義するパスを定義します。
constants/routes.jsとして以下のようにします。
export const SHOW = '/';
export const CREATE = '/create';
export const UPDATE = '/update/:id';
export const BUILD_UPDATE = (id) => `/update/${id}`;
Read, Create, Update, Delete機能のコードは以下です。
まず、表示機能のShow.jsは以下です。
import React from "react";
import "../App.css";
import { Link } from "react-router-dom";
import { CREATE, BUILD_UPDATE } from "../constants/routes";
import _ from "lodash";
import axios from "axios";
import { dataFormat } from "../format/dateFormat";
class Show extends React.Component {
constructor(props) {
super(props);
this.state = {
tasks: {},
};
}
componentDidMount() {
axios.get("http://localhost:5000/api/todos").then((res) => {
console.log(res.data);
this.setState({
tasks: res.data,
});
});
}
render() {
return (
<div>
<h3>
<Link to={CREATE}>タスク作成</Link>
</h3>
<table>
<thead>
<tr>
<th>タイトル</th>
<th>詳細</th>
<th>期限</th>
</tr>
</thead>
<tbody>
{_.map(this.state.tasks, (value) => {
return (
<tr key={value._id}>
<td>
<Link
to={{ pathname: BUILD_UPDATE(value._id), state: value }}
>
{value.title}
</Link>
</td>
<td>{value.detail}</td>
<td>{dataFormat(value.deadline)}</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
}
export default Show;
次に、Create.jsは以下です。
import React from "react";
import { SHOW } from "../constants/routes";
import axios from "axios";
class Create extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
detail: "",
deadline: "",
};
this.handleChange = this.handleChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value,
});
console.log(this.state);
}
onSubmit() {
axios
.post("http://localhost:5000/api/todos", this.state)
.then((res) => {
console.log(res);
this.setState({
title: "",
detail: "",
deadline: "",
});
this.props.history.push(SHOW);
})
.catch((err) => {
console.error(err);
});
}
render() {
return (
<div>
<h4>タスク作成</h4>
<form>
<table>
<tbody>
<tr>
<td>タイトル:</td>
<td>
<input
type="text"
name="title"
onChange={this.handleChange}
></input>
</td>
</tr>
<tr>
<td>タスク名:</td>
<td>
<input
type="text"
name="detail"
onChange={this.handleChange}
></input>
</td>
</tr>
<tr>
<td>期限:</td>
<td>
<input
type="date"
name="deadline"
onChange={this.handleChange}
></input>
</td>
</tr>
</tbody>
</table>
</form>
<button type="submit" onClick={this.onSubmit}>
タスク作成
</button>
</div>
);
}
}
export default Create;
そして、Update.jsは以下です。
import React from "react";
import { SHOW } from "../constants/routes";
import axios from "axios";
import { dataFormat } from "../format/dateFormat";
class Update extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
detail: "",
deadline: "",
};
this.handleChange = this.handleChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.deleteTask = this.deleteTask.bind(this);
}
componentDidMount() {
axios
.get(`http://localhost:5000/api/todos/${this.props.match.params.id}`)
.then((res) => {
console.log(res.data);
this.setState({
...res.data,
});
});
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value,
});
}
onSubmit() {
axios
.patch(
`http://localhost:5000/api/todos/${this.props.match.params.id}`,
this.state
)
.then((res) => {
console.log(res);
this.setState({
title: "",
detail: "",
deadline: "",
});
this.props.history.push(SHOW);
})
.catch((err) => {
console.error(err);
});
}
deleteTask() {
axios
.delete(`http://localhost:5000/api/todos/${this.props.match.params.id}`)
.then((res) => {
console.log(res.data);
this.props.history.push(SHOW);
})
.catch((error) => {
console.log("Remove failed:" + error.message);
});
}
render() {
return (
<div>
<h4>タスク更新・削除</h4>
<form>
<table>
<tbody>
<tr>
<td>タイトル:</td>
<td>
<input
type="text"
name="title"
value={this.state.title}
onChange={this.handleChange}
></input>
</td>
</tr>
<tr>
<td>タスク名:</td>
<td>
<input
type="text"
name="detail"
value={this.state.detail}
onChange={this.handleChange}
></input>
</td>
</tr>
<tr>
<td>期限:</td>
<td>
<input
type="date"
name="deadline"
value={dataFormat(this.state.deadline)}
onChange={this.handleChange}
></input>
</td>
</tr>
</tbody>
</table>
</form>
<table>
<tbody>
<tr>
<td>
<button type="submit" onClick={this.onSubmit}>
タスク更新
</button>
</td>
<td>
<button onClick={this.deleteTask}>削除</button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
export default Update;
ここで、dateのフォーマットの関数として以下で定義します。
react/format/dataFormat.js
import moment from "moment";
export const dataFormat = (date) => {
return moment(date).format("YYYY-MM-DD");
};
以上で、React側の実装は終了です。次にバックエンドの実装をしていきます。
Expressのソースコード
Expressを使用して、各APIを実装していきます。
仕様は以下です。
GET /api/todos/ :全てのTodoを取得
POST /api/todos/:Todoを登録
PATCH /api/todos/:id :指定したidのTodoを更新
Delete /api/todos/:id:指定したidのTodoを削除
以上の仕様を満たすapiをExpressで実装していきます。
まず、express用のディレクトリ作成します。
mkdir react-express-mongo/express
cd express
まず、今回のアプリに必要なパッケージをインストールします。
npm install express cors moment mongoose
const dbConfig = {
URL: "mongodb://127.0.0.1:27017",
DB_NAME: "test",
}
module.exports = dbConfig;
ここでは、データベース名としてtestを用いています。
mongodbを起動させた後、mongoshellを用いて、以下のコマンドでtestを使用するようにします。
$ mongo
$ use test
続いて、データを格納するオブジェクトのmodelの定義を行います。
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const TodoSchema = new Schema({
title: String,
detail: String,
deadline: Date,
});
module.exports = mongoose.model('Todo', TodoSchema);
前回のfirebaseで作成したものと同様に、titile, detail はString, deadlineはDateとして定義します。
次に、実際にapiを実装するapp.jsを以下に示します。
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const dbConfig = require("./config/dbConfig");
const Todo = require("./models/todo");
mongoose
.connect(dbConfig.URL + "/" + dbConfig.DB_NAME, { useNewUrlParser: true })
.then(() => {
console.log("mongodb connection is ok!");
})
.catch((err) => {
console.log("mongodb connection is error", err);
});
const app = express();
app.use(express.json());
app.use(cors());
// ToDo一覧の取得
app.get("/api/todos", (req, res) => {
Todo.find().then((todos) => {
res.json(todos);
});
});
// ToDoの新規登録
app.post("/api/todos", (req, res, next) => {
const { title, detail, deadline } = req.body;
console.log(req.body);
if (typeof title !== "string" || !title) {
const err = new Error("title is required");
err.statusCode = 400;
return next(err);
}
const aTodo = new Todo({
title: title,
detail: detail,
deadline: new Date(deadline),
});
aTodo
.save(() => {
console.log("save success");
res.status(201).end();
})
.catch((err) => {
console.log("save failed", err);
res.status(400).end();
});
});
// 指定されたIDのToDoを取得
app.get("/api/todos/:id", (req, res, next) => {
const targetId = req.params.id;
Todo.findById(targetId)
.then((todo) => {
res.json(todo);
})
.catch((err) => {
console.error("id is not found", err);
res.status(400).end();
});
});
// ToDoの削除
app.delete("/api/todos/:id", (req, res) => {
const targetId = req.params.id;
Todo.findByIdAndDelete(targetId)
.then((result) => {
console.log("delete success", result);
res.status(201).end();
})
.catch((err) => {
console.error("delete failed", err);
res.status(400).end();
});
});
// 指定されたIDのToDoを更新
app.patch("/api/todos/:id", (req, res, next) => {
const targetId = req.params.id;
const updatedTodo = req.body;
Todo.findByIdAndUpdate(targetId, updatedTodo)
.then((result) => {
console.log("update success", result);
res.status(201).end();
})
.catch((err) => {
console.error("id is not found", err);
res.status(400).end();
});
});
// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
console.error(err);
res.status(err.statusCode || 500).json({ error: err.message });
});
app.listen(5000);
以上で、react, expressの実装が完了しました。
動作確認するためにreactディレクトリでnpm start, expressディレクトリでnode app.jsを実行すると動作確認することができます。
コメント