モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

MERNことはじめ

背景

MERN 開発の流れを知りたくてyotuube(https://www.youtube.com/watch?v=7CqJlxBYj-M)を見た。
動画は情報量が多くて捗るけど、その分繰り返し見るには時間がかかるので文字に起こした。

MongoDBの説明

  • Database
  • Collection
  • Document
  • Index
  • $lookup
  • Reference

Example

Mongo DBの例

{
    name:" ",
    title:" ",
    address:{
                address1:" ",
                city:" ",
                state: " ",
            },
    topics:[" ", " ", " ",],
    number:
}

MogoDB Atlasの紹介

512MBまで無料、Cluster使えるとか

作成するアプリの説明

Appは以下を含む

  • Exercises
  • Users

全てのエクササイズは一人のユーザーのみ持つ

ディレクトリの作成と環境の整備

特定のディレクトリで、

create-react-app mern-exercise-tracker

数分待って完了したらディレクトリ変更

cd mern-exercise-tracker

バックエンド用のディレクトリを作成し環境もろもろを整備する

mkdir backend
cd backend
npm install express cors mongoose dotenv
npm install -g nodemon

バックエンドとMongoDBの接続

backendディレクトリにserver.jsファイルを新規作成する。

const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');

require('dotenv').config();

const app = express();
const port = process.env.PORT || 5000;

app.use(cors());
app.use(express.json());

const uri = process.env.ATLAS_URI;
mongoose.connect(uri, {userNewUrlParser: true, userCreateIndex: true });

const connection = mongoose.connection;
connection.once('open', () => {
    console.log("MongoDB database connection established successfully");
})

const exercisesRouter = require('./routes/exercises');
const usersRouter = require('./routes/users');

app.use('/exercises', exercisesRouter);
app.use('/users', usersRouter);


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

backendディレクトリに.envファイルを新規作成する。
MongoDB Atlas clusterのconnection clusterのconnection String OnlyのCopyをクリック

.envに以下を記載。 ATLAS_URI=ペースト(先ほどコピーしたもの) passwordの箇所にパスワードを記載する。

backendディレクトリでnodemon server

以上でMongoDBにアクセスできるようになる。

modelの作成

backendにmodelsフォルダを作成。
modelsフォルダに

  • exercise.model.js
  • user.model.js

を新規作成する。
exercise.model.js

const mongoose = require('monngoose');

const Schema = mongoose.Schema;

const exerciseSchema = new Schema({
    username: { type: String, required: true},
    description: { type: String, required: true},
    duration: { type: Number, required: true},
    date: { type: Date, required: true},
}, {
    timestamps: true,
});

const Exercise = mongoose.model('Exercise', exerciseSchema);

module.exports = Exercise;

user.mode.js

const mongoose = require('monngoose');

const Schema = mongoose.Schema;

const userSchema = new Schema({
    username:{
        type: String,
        required: true,
        unique: true,
        trim: true,
        minlength: 3
    },
}, {
    timestamps: true,
});


const User = mongoose.model('User', userSchema);
module.exports = User;

Routeの作成

backendディレクトリ内にroutesディレクトリを作成する。
routesディレクトリ内に

  • exercises.js
  • users.js

を新規作成する。

exercises.js

const router = require('express').Router();
let Exercise = require('../models/exrecise.model');

router.route('/').get((req, res) => {
    Exercise.find()
        .then(exercises => res.json(exercises))
        .catch(err => res.status(400).json('Error: ' + err));
});

router.route('/add').post((req, res) => {
    const username = req.body.username;
    const description = req.body.description;
    const duration = Number(req.body.duration);
    const date = Date.parse(req.body.date);

    const newExercise = new Exercise({
        username,
        description,
        duration,
        date,
    });

    newExercise.save()
    .then(() => res.json('Exercise added!'))
    .catch(err => res.status(400).json('Error: ' + err));
});

module.exports = router;

user.js

const router = require('express').Router();
let User = require('../models/user.model');

router.route('/').get((req, res) => {
    User.find()
        .then(users => res.json(users))
        .catch(err => res.status(400).json('Error: ' + err));
});

router.route('/add').post((req, res) => {
    const username = req.body.username;
    const newUser = new User({username});

    newUser.save()
        .then(() => res.json('User added!'))
        .catch(err => res.status(400).json('Error: ' + err));
});

module.exports = router;

server.jsにルートを記載する(上述)。
---ここまで動画開始から30分---

データベースの確認

Insomniaでサーバーの動作を確認する。
exercises.jsにIDのルートの処理を追加する(上述)。
get,delete,postなど

---ここまで動画開始から43分。ここからReactの説明開始---

ReactでHello Worldの表示

publicのindex.htmlを確認する
index.jsを確認する
App.jsに移動。

exercise-trackerフォルダで

  • npm install bootstrap
  • npm install reat-router-dom

使用するコンポーネントをあらかじめimportする。
srcディレクトリ下にcomponentディレクトリを作成する。

その後

これ以降のReactの説明に関する情報は少し古い。
Hooksを使用するのが今後のトレンドになるはず。
従ってこれ以降は動画をまとめる必要はないような気がするので
代わりにReact Hooksについて読みかじったことを記載しておく。

参考文献

りあクト!
りあクト! TypeScriptで始めるつらくないReact開発 第2版 - くるみ割り書房 - BOOTH

react training公式ページ
React Training: React Router v5.1

Qiitaの記事

useStateの使い方

import React, {FC, useState } from 'react';
import { Button, Card, Statistic } from 'semantic-ui-react';

import './App.css';

const App:FC = () => {
    const [count, setCount] = usetState(0);
    const increment = () => {
        setCount(count + 1);
    };
    const decremet = () => {
        setCount(count -1);
    };

    return(
        <div className="container">
            <header>
                <h1>カウンター</h1>
            </header>
            <Card>
                <Statistic className="number-board">
                    <Statistic.Label>count</Statistic.Label>
                    <Statistic.value>{count}</Statistic.value>
                </Statistic>
                <Card.Content>
                    <div className="ui two buttons">
                        <Button color="red" onClick={decrement}>-1</Button>
                        <Button color="green" onClick={increment}>+1</Button>
                    </div>
                </Card.Content>
            </Card>
        </div>
    );
};

export default App;

useEffectの使い方

import React, {FC, useEffect, useState} from 'react';
import { Button, Card, Icon, Statistics } from 'semantic-ui-react';

import './App.css';

const LIMIT = 60;

const App:FC = () => {
    const [timeLeft, setTimeLeft] = useState(LIMIT);
    const reset = () => {
        setTimeLeft(LIMIT);
    };

    const tick = () => {
        setTimeLeft(prevTime => (prevTime === 0? LIMIT: prevTime -1));
    };

    useEffect(() => {
        const timerId = setInterval(tick, 1000);
        return () => clearInterval(timerId);
    },[]);

    return (
        <div className="container">
            <header>
                <h1>タイマー</h1>
            </header>
            <Card>
                <Statistic className="number-board">
                    <Statistics.Label>time</Statistics.Label>
                    <Statistics.value>{timeLeft}</Statistics.value>
                </Statistic>
                <Card.content>
                    <Button color="red" fluid on Click={reset}>
                        <Icon name="redo" />
                        Reset
                    </Button>
                </Card.content>
            </Card>
        </div>
    );
};

export default App;

Custom Hooksの例

import React, { FC, useEffect, useState } from 'react';
import AppComponent from ' ../components/App';

//Custom hook "useTimer"
const useTimer = (limitSec: number):[number, () => void] => {
    const [timeLeft, setTimeLeft] = useState(limitSec);

    const reset = () => {
        setTimeLeft(limitSec);
    };

    const tick = () => {
        setTimeLeft(prev => ( prevTime === 0? limitSec : prevTime -1));
    };

    useEffect(() => {
        const timerId = setInterval(tick, 1000);
        return () => clearInterval(timerId);
    },[]);

    return [timeLeft, reset];
}

const AppContainer: FC= () => {
    const LIMIT = 60;
    const [timeLeft, reset] = useTimer(LIMIT);

    return <AppComponent timeLeft={timeLeft} reset={reset} />;
};

export default AppContainer;

react-routerの例

import React from 'react';
import ReactDom from 'react-dom';
import {
    Browsewrrouter as router,
    Route,
    Switch,
    useParams,
    useHistory,
    useLocation,
} from 'react-router-dom';

function Hello(){
    const history = useHistory();
    return(
        <div>
            <h1>Hello</h1>
            <button onClick={() => history.push('/hello/react-router?message=hooks#test')}>Next</button>
        </div>
    );
}

function HelloSomeone(){
    const history = useHistory();
    const location = useLocation();
    const {name} = useParams();
    return(
        <div>
            <h1>Hello {name}</h1>
            <p>pathname: {location.pathname}</p>
            <p>search: {location.search} </p>
            <p>hash: {location.hash}</p>
            <button onClick={() => history.goBack()}>Go Back</button>
        </div>
    );
}

function App(){
    return (
        <Router>
            <Switch>
                <Route path="/" exact>
                    <Hello />
                </Route>
                <Route path="/hello/:name" exact>
                    <HelloSomeone/>
                </Route>
            </Switch>
        </Router>
    );
}

react-reduxの例