React / Express で作る Web アプリケーション開発入門|研修コースに参加してみた

今回参加したコースは React / Express で作る Web アプリケーション開発入門 です。
DX の需要が高まる中、スタートアップさながらに少人数で、フルスタックに、スピードよく開発することが増えています。
それを実現する方法として Ruby / RoR がフォーカスされてきましたが、フロントエンドで React / Vue といった JavaScript フレームワークが主流になり、バックエンドも同じ JavaScript を使う開発が増えています。
このコースでは、その JavaScript 製の React / Express + Node.js による Web アプリケーションを 1 日で学びながら開発しました。 1 日で本当にできてしまったので、 Java 全盛の時代では考えられないスピードです。
では、どのような内容だったのか、レポートします!
もくじ
コース情報
想定している受講者 |
|
---|---|
受講目標 |
|
講師紹介
もともとテクノロジでの登壇が多かったのですが、最近では IT 入門者から流行の技術まで担当する領域が広がっている 冨原 祐 さんが登壇されました。

「講師を一生の仕事にする」
早速、コースの進め方を紹介いただきました。
- サーバサイドとフロントエンドを別々に開発
- 演習の進め方: スキルに合わせて 3 段階のやり方を用意
- 事前の解説
- 講師のライブプログラミングを見る
- 穴あき問題 (コメントアウトしているところ) を自分で書く
- ↑ ができたらサンプル以外の書き方で実装する
- 演習内容は 12 題
- 1 ~ 4: フロントエンド
- 5 ~ 9: バックエンド
- 10 ~ 12: フロントエンド + バックエンド
演習上手な冨原さんらしい「自分で工夫する」時間が設けられていますね。
なお、事前に穴あき問題のサンプルコードは共有されていて、わからなくてもキャッチアップできるようになっています。
続いて、このコースで使う技術構成です。
- フロントエンド: React.js + Axios (HTTP 通信用)
- バックエンド: Node.js + Express + Prisma (OR マッパー) + MySQL
コースで開発するのは 書籍を管理する Web アプリケーションです。


なお、エディタは Visual Studio Code を使用します。
React.js の特徴と環境構築
まずはフロントエンドの環境構築からです。
初めに React について特徴を解説いただきましたが、以前のレポートにもありますので、ここでは割愛します。
3 時間で React 入門 ~ GraphQL と Relay で作る Web アプリケーション~|研修コースに参加してみた
環境構築
このあと環境構築の説明をいただきましたが、このコースではすでに完了した環境を用意いただいています。
- npm と npx で環境を構築する
- npm: JavaScript のパッケージマネージャ
- npx: インストールしていないパッケージを CLI で実行できる
以前レポートした React のコースでも npx を使いましたが、便利すぎますし、高速すぎます !!
では、フロントエンドをビルドしましょう。
> npm start
> front@0.1.0 start
> react-scripts start
Compiled successfully!
You can now view front in the browser.
Local: http://localhost:3000
On Your Network: http://10.15.0.4:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
ビルドできたことが確認できました。

React でフロントエンドを開発
いよいよ演習問題の開始です! まずはフロントエンドの実装から。
step 1. 本の一覧を出す (book.js に必要なコードを書こう)
- src 配下に books ディレクトリを作成し、 BooksList.js を作成
import React from "react"; //Todo: css ファイル (BookList.css) のインポートを行う。 export default class BookList extends React.Component{ constructor(props) { super(props); this.state = { books:[ {title:"坊っちゃん", author:"夏目漱石", overview:"ぼっちゃんが先生になって頑張ったけど結局ダメでした。"} ,{title:"吾輩は猫である", author:"夏目漱石", overview:"吾輩は猫ですが失恋してから人間の前足の使い方が不思議に思えました。"} ,{title:"走れメロス", author:"太宰治", overview:"激怒して走ったら結構速かったので友人と殴り合いました。"} ] , } } render(){ const { books } = this.state; return ( <div> <div className="bookListMain"> <div className="bookListHeader"> ここに新規追加を予定 <button>追加</button> </div> <div className="bookListBody"> <table> <tr> <th>タイトル</th> <th>著者</th> <th>概要</th> <th>操作</th> </tr> {books.map((book,index) => <tr class="bookrow"> {{/* Todo:ここに表示したい内容を記述 */ }} <td class="action"> <button>編集</button> <button>削除</button> </td> </tr> )} </table> </div> </div> </div> ); } }
ご覧の通り、ダミーデータの配列を用意して、それを state に保持している状態です。
この book.js を次のステップ以降、 CRUD の処理を追加して完成させます。
step 2. 本の追加機能を追加 (入力を state で持つ)
<div className="bookListHeader">
ここに新規追加を予定
<button>追加</button>
</div>
この追加機能を実装します。
なお、解答のサンプルコードはコース受講時のお楽しみ、ということで、このレポートでは冨原さんにライブコーディングいただいた手順を紹介します。
- input 属性を追加し、 onChange で値を保持。 onInput メソッドを呼び出す
<div className="bookListHeader"> タイトル:<input type="text" name="title" onChange={this.onInput} /><br /> 著者 :<input type="text" name="author" onChange={this.onInput} /><br /> 概要 :<textarea rows="3" cols="50" name="overview" onChange={this.onInput} /><br /> <button>追加</button> </div>
- onInput メソッドを書く
//画面で何か入力された時の処理 onInput = (e) => { //Todo:setStateを利用して入力値をstateに保持 }
- 追加ボタンを押すと state に値を追加する addBook メソッドを実装
タイトル:<input type="text" name="title" onChange={this.onInput} /><br /> 著者 :<input type="text" name="author" onChange={this.onInput} /><br /> 概要 :<textarea rows="3" cols="50" name="overview" onChange={this.onInput} /><br /> <button onClick={this.addBook} >追加</button>
//新規追加ボタン処理 addBook = () => { //Todo:stateで保持している入力値を利用し、 state の books に 1 件追加 }
step 3. 削除ボタンを押すと、該当のレコードが削除される
<tr class="bookrow">
<td class="title">{book.title}</td>
<td class="author">{book.author}</td>
<td class="overview">{book.overview}</td>
<td class="action">
<button>編集</button>
<button>削除</button>
</td>
</tr>
この削除ボタンの機能を実装します。
- 削除ボタンに onClick で deleteBook メソッドを動かす
{{/* Todo:ボタンのonClickを設定 */}} <button>削除</button>
- ただしレコードの行を指定する引数 index が必要
- その場合
onclick={this.deleteBook(index)}
だとレンダリング時に消してしまう - アロー関数を使って、関数を受け取る
//削除ボタン処理 deleteBook = (index) => { //削除処理を追加 }
- React ではよくアロー関数を使う
- deleteBook メソッドを実装する
step 4. モーダルを開いてレコードを編集する
編集ボタンを押すと、該当レコードの編集画面がモーダルで開き、更新ボタンを押すとデータが更新できる機能を実装します。
<tr class="bookrow">
<td class="title">{book.title}</td>
<td class="author">{book.author}</td>
<td class="overview">{book.overview}</td>
<td class="action">
<button>編集</button>
<button>削除</button>
</td>
</tr>
この編集ボタンの更新機能を追加します。
- state に showModal (Boolean) を追加し、モーダルウインドウを書く
{showModal && <div id="overlay"> <div id="content"> タイトル:<input type="text" name="modTitle" value={this.state.modTitle} onChange={this.onInput} /><br /> 著者 :<input type="text" name="modAuthor" value={this.state.modAuthor} onChange={this.onInput} /><br /> 概要 :<textarea rows="3" cols="50" name="modOverview" value={this.state.modOverview} onChange={this.onInput} /><br /> <button onClick={this.saveBook} >更新</button> <button onClick={this.toggleModal} >閉じる</button> </div> </div> }
- モーダル表示をコントロールする toggleModal メソッドを書く
//モーダルウィンドウの表示切り替え toggleModal = () => { const{showModal} = this.state; this.setState({ showModal: !showModal }); }
- 編集ボタンを押して、モーダルで該当レコードのデータを編集画面を開く modBook メソッドを追加
<button onClick={() =>{this.modBook(index)}}>編集</button>
//編集ボタン処理(ここでは編集画面を開くだけ) modBook = (index) => { const { books, modTitle, modAuthor, modOverview } = this.state; //Todo:setStateで編集対象のデータを画面に反映 this.toggleModal(); }
- 編集画面 (モーダル) の更新内容を state に保存する saveBook メソッドを追加
//モーダルウィンドウ上の更新ボタン処理 saveBook = () => { const { books, modIndex, modTitle, modAuthor, modOverview } = this.state; var book = {title:modTitle, author:modAuthor, overview:modOverview}; //Todo:作成したbookをstate上のbooksに戻す。 this.toggleModal(); }
なお今回は rendor ですべての要素を表示していましたが、普通はもっと細かにコンポーネントを区切って行います。
ここまででフロントエンドの作業は完了です。
Express + Prisma で API サーバを書く
続いて、バックエンドです。具体的には API サーバを開発します。
まずは Node.js を説明いただき、今回使用するフレームワークなどを解説いただきました。
- Express
- Node.js で動く Web アプリケーションフレームワーク
- Prisma
- OR マッパー。他にも Node.js で人気の ORM はある
- TypeScript とも相性がよい
環境構築
では、ビルドしましょう。
フロントエンドと違って、今回のバックエンドは少ないコマンドで出来るので、一緒にやってみます。
- Express 環境を構築する express-generator をインストール
> npm i express-generator npm WARN deprecated mkdirp@0.5.1: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.) added 10 packages, and audited 11 packages in 4s 3 moderate severity vulnerabilities To address all issues, run: npm audit fix Run `npm audit` for details.
- ejs をテンプレートエンジンにした新しいプロジェクトを作成
> npx express --view=ejs . destination is not empty, continue? [y/N] y create : public\ create : public\javascripts\ create : public\images\ create : public\stylesheets\ create : public\stylesheets\style.css create : routes\ create : routes\index.js create : routes\users.js create : views\ create : views\error.ejs create : views\index.ejs create : app.js create : package.json create : bin\ create : bin\www install dependencies: > npm install run the app: > SET DEBUG=server:* & npm start
- サーバのポートを 8080 に変更 ( 3000 はフロントで使用中)
- bin/www ファイルの 15 行目を編集
var port = normalizePort(process.env.PORT || '8080');
- bin/www ファイルの 15 行目を編集
- 必要なパッケージをすべてインストール
> npm install added 53 packages, removed 9 packages, and audited 55 packages in 8s found 0 vulnerabilities > SET DEBUG=server:* # 環境変数を設定
これで必要なパッケージはインストールできました。起動してみましょう。
> npm start
> server@0.0.0 start
> node ./bin/www
GET /books 404 68.665 ms - 1103

続いて、 Prisma もビルドしましょう。なお、 MySQL はすでにインストール and 構築 / テーブル作成済みです。
- prisma をインストール
> npm i @prisma/client # @prisma/client で別名に出来る added 2 packages, and audited 57 packages in 6s found 0 vulnerabilities > npx prisma init Need to install the following packages: prisma Ok to proceed? (y) y ✔ Your Prisma schema was created at prisma/schema.prisma You can now open it in your favorite editor. Next steps: 1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started 2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver or mongodb (Preview). 3. Run npx prisma db pull to turn your database schema into a Prisma schema. 4. Run npx prisma generate to generate the Prisma Client. You can then start querying your database. More information in our documentation: https://pris.ly/d/getting-started
- env ファイルの DATABASE の接続先を変更
DATABASE_URL="mysql://seplus:seplus@localhost:3306/booksdb"
- prisma の設定ファイル (schema.prisma) にデータベースの定義 (マイグレーション) を書く
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model books{ id Int @id @default(autoincrement()) title String author String overview String created_at DateTime @default(now()) updated_at DateTime @default(now()) }
- マイグレーションを実行
> npx prisma migrate dev --name init --preview-feature Environment variables loaded from .env Prisma CLI does not include a `dev` command any more right now. If you want to run Prisma Studio, use prisma studio. If you want to generate the Prisma Client, use prisma generate (or prisma generate --watch) If you want to update your schema, use prisma db pull. If you want to migrate your database, use prisma migrate.
- コードを自動生成
> npx prisma generate Environment variables loaded from .env Prisma schema loaded from prisma\schema.prisma ✔ Generated Prisma Client (3.4.2) to .\node_modules\@prisma\client in 316ms You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client ``` import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() ```
- Prisma には GUI のデータベース操作ツール Prisma studio があるので操作してみる
> npx prisma studio
ORM にもクライアント機能が追加されているのですね! DB のクライアントツールとどっちが美味しいのか、と思って調べてみると、なるほどクエリとなる JSON を吐いてくれるんですね。これは便利!
実際に設定できたかどうか、 Prisma Studio で確認してみます。

GET で JSON を返すエンドポイントを作成
では、 API を実装しましょう。まずは GET の実装です。
/books をルーティングに追加
その前にルーティングです。 users というエンドポイントはデフォルトで用意されているので、 books を追加し、ダミーデータを返すようにします。
- routes/books.js を追加
var express = require('express'); var router = express.Router(); /* GET books listing. */ router.get('/', function(req, res, next) { res.json({ title: '吾輩は猫である' }); }); module.exports = router;
- app.js にルートを追加
var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var booksRouter = require('./routes/books'); // 中略 app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/books', booksRouter);
確認してみましょう。

このルーティングの処理は他言語のフレームワークなどと変わりは無いですね。
データベースから index を取得する
GET の all (書籍リスト) を返す処理を書きましょう。
- books.js の GET メソッドに findMany で index を取得する処理を追加
router.get('/', async(req, res, next) => { const prisma = new PrismaClient(); const allBooks = await prisma.books.findMany(); res.json(allBooks); });
ORM があると簡単かつ直感的に書けてうれしいですね。
また、確認してみましょう。

POST の処理を追加
続いて、 POST の実装です。
GET のようにブラウザ表示で確認できないので、 Visual Studio Code の拡張機能 Rest Client を追加してテストも実行します。
- books.js に POST メソッドを追加。 create を使う
// 新規データ作成 router.post('/', async(req, res, next) => { const prisma = new PrismaClient(); const data = {data:req.body}; const book = await prisma.books.create(data); res.json(book); });
- /test ディレクトリを追加しテストファイル bookPostTest.http (ファイル名拡張子は何でも OK ) を作成
POST http://localhost:8080/books/ HTTP/1.1 content-type: application/json { "title": "星の王子さま", "author": "サン=テグジュベリ", "overview": "王子様がいろんな星を旅したら蛇に噛まれて消えました。" }
- Rest Client で検証する
bookPostTest.http を開くと Send Request が表示されているのでクリックすると Rest Client の結果が表示されます。

レスポンスも確認してみましょう。
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 248
ETag: W/"f8-9g2+oxP44dzPw+1b/u6R1HdtDFE"
Date: Mon, 15 Nov 2021 07:45:53 GMT
Connection: close
{
"id": 4,
"title": "星の王子さま",
"author": "サン=テグジュベリ",
"overview": "王子様がいろんな星を旅したら蛇に噛まれて消えました。",
"created_at": "2021-11-15T07:45:53.730Z",
"updated_at": "2021-11-15T07:45:53.730Z"
}
PUT と DELETE の処理を追加
最後は PUT と DELETE です。
- 同じように books.js に put と delete メソッドを追加。 update と delete を使う
// 更新 router.put('/', async(req, res, next) => { const prisma = new PrismaClient(); //prismaの更新は where:とdata:で指定する。 //{where: {条件},data:{key:value,key:value}} const data = {where:{id:req.body.id},data:req.body}; const book = await prisma.books.update(data); res.json(book); }); // 削除 router.delete('/', async(req, res, next) => { const prisma = new PrismaClient(); //prismaの削除は{where:{条件}}で指定する。 const data = {where:{id:req.body.id}}; const book = await prisma.books.delete(data); res.json(book); });
- テストファイルを追加
- bookPutTest.http を追加
PUT http://localhost:8080/books/ HTTP/1.1 content-type: application/json { "id": 4, "title": "星の王子さま2", "author": "サン=テグジュベリ", "overview": "王子様が帰ってきましたが前回と全く同じ話でした。" }
- bookDeleteTest.http
DELETE http://localhost:8080/books/ HTTP/1.1 content-type: application/json { "id": 3 }
- bookPutTest.http を追加
- Rest Client でテスト
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 245 ETag: W/"f5-lMcn+DFafxiyLxK9jgHL3dDysfU" Date: Mon, 15 Nov 2021 07:57:50 GMT Connection: close { "id": 4, "title": "星の王子さま2", "author": "サン=テグジュベリ", "overview": "王子様が帰ってきましたが前回と全く同じ話でした。", "created_at": "2021-11-15T07:45:53.730Z", "updated_at": "2021-11-15T07:45:53.730Z" }
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 149 ETag: W/"95-ALxvJhrYDXETpV/jJBxHZiBZ9/Q" Date: Mon, 15 Nov 2021 07:58:31 GMT Connection: close { "id": 3, "title": "銀河英雄伝説", "author": "田中芳樹", "overview": "銀河の歴史がまた一ページ", "created_at": "2021-11-15T07:10:09.850Z", "updated_at": "2021-11-15T07:10:09.850Z" }
簡単な CRUD 処理なら ORM でサクサク書けますね。なお、通常ならバリデーションも書きますが、このコースでは割愛します。
これで CRUD を追加できたので、 API サーバは一旦完成です。
API からデータを取得する (バックエンドとフロントエンドを繋ぐ)
最後にダミーデータを返すようにしていた books のエンドポイントを差し替え、フロントエンドから API にリクエストを投げて、データを取得するようにします。
- ダミーで指定していた books 配列を空にする (フロントエンド BookList.js)
export default class BookList extends React.Component{ constructor(props) { super(props); this.state = { books: [] , // 中略 } } }
- API からデータを取得する componentDidMount メソッドを追加。初期データの取得には fetch を使う (フロントエンド BookList.js)
//render後に自動で動作する componentDidMount(){ fetch("/books") .then(res => res.json()) .then(json => { console.log(json); this.setState({ books:json }) }); }
- ただ、これでは CORS (Cross-Origin Resource Sharing ) エラーを起こす
- localhost:3000 と localhost:8080 があるため
- フロントエンドの package.json に proxy を追加
"proxy": "http://localhost:8080", "dependencies": { "@testing-library/jest-dom": "^5.15.0", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", "axios": "^0.24.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", "web-vitals": "^1.1.2" },
同じように確認してみましょう。

Axios を使って POST を実装
最後にフロントエンドから POST するときは state だけに保持されたものの、 DB にはストアできていない状態でした。これを先程の fetch ではなく、 HTTP クライアントである Axios を使えば簡単に書けるのでやってみましょう。
- Axios を import する
import axios from "axios";
- addBook を変更
addBook = () => { const { title, author, overview } = this.state; const data = {title:title, author:author, overview:overview}; axios.post("/books", data) .then(json => { console.log(json); this.componentDidMount(); }); }
これは簡単に書けて、便利ですね!
この Axios を使った POST の処理を実装したところで、このコースは修了しました。
まとめ
いまフロントエンドで主流の React と、バックエンドも同じ JavaScript で実装できる Node.js + Express + Prisma で Web アプリケーションを開発してみました。
今回の演習では、事前のライブコーディング + 穴あき問題と解答のサンプルコードが用意されていたので、つまづくことも少なく、サクサクと進められました。コードそのものが書けるようになるには、まだまだ訓練が必要ですが、開発の進め方、特にモダンな JavaScript のエコシステムに乗っかると、とっても楽できることがわかりました。
DX で開発のスピードが求められるようになり、ササッと MVP を実装する場面も増えています。そういったスピードよく開発できる術を学ぶにはとてもオススメのコースでした!
label SEカレッジを詳しく知りたいという方はこちらから !!

IT専門の定額制研修 月額28,000円 ~/ 1社 で IT研修 制度を導入できます。
年間 670 コースをほぼ毎日開催中!!

SEプラスにしかないコンテンツや、研修サービスの運営情報を発信しています。