見出し画像

NodeJs(ExpressJs)でCRUDアプリケーションを作る初心者ガイド

この記事は弊社のフルスタックエンジニア:Ashish Dhakalが作成した記事です。英語版はこちらをご覧ください。
Medium

CRUD (Create、Read、Update、Delete) アプリケーションの作成は、データベースとやり取りするために必要な基本的な操作であるため、すべての開発者が持つべき基礎スキルの1つです。このガイドでは、サーバーサイド・アプリケーションの開発で人気のあるNode.jsとExpress.jsを使用して、CRUDアプリケーションを構築するための包括的な理解を初心者に提供することを目的としています。

このチュートリアルが終わる頃には、あなた自身の Node.js プロジェクトをセットアップし、RESTful API を設計し、データベースとやり取りして重要なデータ操作を実行する自信がついていることでしょう。あなたが新しい開発者であれ、スキルをリフレッシュしたいだけであれ、このガイドは実用的でわかりやすい出発点となるでしょう。

ステップ1:プロジェクトのセットアップ

1.新しいプロジェクト・ディレクトリを作成する: ターミナルを開き、プロジェクト用の新しいフォルダを作成する。

mkdir nodejs-todo-api cd nodejs-todo-api

2.新しいNode.jsプロジェクトを初期化する: プロジェクト・フォルダー内で、新しいNode.jsプロジェクトを次のように初期化します:

npm init -y

このコマンドは、プロジェクトの依存関係を管理するためのpackage.jsonファイルを生成する。

3. 必要な依存関係をインストールする: 必要なライブラリをインストールします:

npm install express mysql dotenv body-parser nodemon

依存関係の説明:

  • express : API ルーティングとミドルウェアのセットアップを簡素化する、- Node.js 用の高速でミニマルな Web フレームワーク。 

  • mysql : データベース接続を可能にするNode.js用MySQLドライバ。

  • dotenv : .envファイルから環境変数をロードするモジュール。

  • body-parser : 入力されたリクエストボディを解析し、フォームデータへのアクセスを容易にするミドルウェア。

  • nodemon : コードの変更が検出されると自動的にサーバを再起動するユーティリティ。

4. プロジェクト構造を作成する: 設定、コントローラ、モデル、ルートのディレクトリとファイルを作成して、プロジェクトを整理します:

mkdir config controllers routes models touch index.js config/db.js controllers/todoController.js routes/todoRoutes.js models/todoModel.js .env

プロジェクトの構成は次のようになるはずです:

├── config 
│  └── db.js 
├── controllers 
│   └── todoController.js 
├── models 
│   └── todoModel.js 
├── routes 
│   └── todoRoutes.js 
├── index.js 
├── .env 
└── package.json

5. サーバスクリプトのセットアップ

Nodemonを使用してサーバーを起動するスクリプトをpackage.jsonファイルに追加します:

"scripts": {  
 "start": "nodemon index.js" 
}

これで、サーバーを起動できる:

npm start

6. Nodemonを設定する
特定のファイルまたはディレクトリを監視するためにNodemonを設定するために、プロジェクトルートにnodemon.jsonファイルを作成します:

{     
  "watch": [
         "src", 
        "index.js",
         "*.js"     ], 
    "ext": "js,json",   
  "exec": "node index.js" 
}

ステップ2:環境変数の設定

.envファイルにデータベース設定を追加する

DB_HOST=localhost 
DB_USER=admin 
DB_PASSWORD=123456 
DB_NAME=todo_db 
DIALECT=mysql 
PORT=5000

ステップ 3: Sequelize データベース接続の設定

まず、todo_dbという名前のMySQLデータベースを作成し、Sequelizeインスタンスを作成して、config/db.jsでデータベース接続を確立する:

const { Sequelize } = require('sequelize');

const sequelize = new Sequelize(
    process.env.DB_NAME,
    process.env.DB_USER,
    process.env.DB_PASSWORD,
    {
        host: process.env.DB_HOST,
        dialect: process.env.DIALECT,
    });

module.exports = sequelize;

ステップ4:Todoモデルを定義する

models/todoModel.jsでSequelizeを使ってTodoスキーマを定義します:

const { DataTypes } = require('sequelize');
const sequelize = require('../config/db');

const Todo = sequelize.define(
    'Todo',
    {
        id: {
            type: DataTypes.INTEGER,
            autoIncrement: true,
            primaryKey: true,
        },
        title: {
            type: DataTypes.STRING,
            allowNull: false,
        },
        description: {
            type: DataTypes.STRING,
        },
        status: {
            type: DataTypes.BOOLEAN,
            defaultValue: false,
        },
        createdDateTime: {
            type: DataTypes.DATE,
            defaultValue: DataTypes.NOW,
        },
    },
    {
        tableName: 'Todos',
        timestamps: false,
    }
);

module.exports = Todo;

ステップ5:検証スキーマの作成

受信データが正しく構造化されていることを確認するには、適切なバリデーションが重要です。このステップでは、人気のスキーマ記述言語でありJavaScript用のデータバリデータであるJoiを使用します。まず、Todoエンティティの検証スキーマファイルを作成しましょう。

mkdir validators
touch validators/todoValidator.js

Joiを使ってスキーマを定義する:validators/todoValidator.jsの中に以下のコードを追加する:

const Joi = require('joi');

// Define the schema for validating Todo input data
const TodoSchema = Joi.object({
    title: Joi.string().required().messages({
        'string.empty': 'Title is required',
        'any.required': 'Title is required',
    }),
    description: Joi.string().optional(),
    status: Joi.boolean().optional(),
});

module.exports = { TodoSchema };

このスキーマはcontrollers/todoController.jsで使用され、CRUD操作を行う前に入力されたデータを検証します。これで、ユーザが Todo を作成または更新しようとすると、サーバはタイトルなどの必須フィールドが提供されていることを確認します。

ステップ 6: 定数とレスポンス処理の定義

コードをクリーンで保守しやすく保つために、HTTPステータスコード用の定数をいくつか定義し、クライアントに送り返すレスポンスを標準化するレスポンス・ハンドラを作成します。これにより、アプリケーション全体でステータスコードをハードコーディングすることを避け、一貫したAPIレスポンスを保証することができます。

1.constants ディレクトリと statusCodes.js ファイルを作成する

mkdir constants
touch constants/statusCodes.js

2. HTTPステータスコードを追加する
constants/statusCodes.jsの中で、よく使われるHTTPステータスコードを定義します:

const StatusCodes = {
    BadRequest: 400,
    NotFound: 404,
    InternalServerError: 500,
    Success: 200,
    Created: 201
};

module.exports = { StatusCodes };

3. response ディレクトリと response.js ファイルを作成する

mkdir response
touch response/response.js

4. レスポンス・ハンドラを作成する
response/response.jsの中で、先に定義したステータス・コードを使用して標準化されたレスポンス・メソッドを定義する:

const { StatusCodes } = require('../constants/statusCodes');

const response = {
    BadRequest: (res, msg) => {
        return res.status(StatusCodes.BadRequest).json({ error: msg });
    },
    InternalServerError: (res, msg) => {
        return res.status(StatusCodes.InternalServerError).json({ error: msg });
    },
    Success: (res, message) => {
        return res.status(StatusCodes.Success).json({ message });
    },
    SuccessData: (res, data, message) => {
        return res.status(StatusCodes.Success).json({ data, message });
    },
    Created: (res, data, message) => {
        return res.status(StatusCodes.Created).json({ data, message });
    },
    NotFound: (res, message) => {
        return res.status(StatusCodes.NotFound).json({ error: message });
    }
};

module.exports = { response };

ステップ 7: Todoコントローラの作成

このステップでは、CRUD操作のコアロジックを処理するコントローラファイルを作成します。コントローラは、モデルとクライアントのリクエストの橋渡しをし、入力されたデータを処理し、モデルを通してデータベースとやりとりし、カスタムレスポンスハンドラを使ってレスポンスを返します。
ファイルの構造
先に進む前に、プロジェクトの構成が以下のようになっていることを確認してください:

├── config
│   └── db.js 
├── constants
│   └── statusCodes.js
├── controllers
│   └── todoController.js 
├── models
│   └── todoModel.js
├── response
│   └── response.js
├── routes
│   └── todoRoutes.js
├── validators
│   └── todoValidator.js
├── index.js
├── .env
└── package.json

todoController.jsに実装されているCRUD操作を分解してみよう:

1.インポートtodoController.jsの先頭で、必要なモジュールをインポートする:

const Todo = require('../models/todoModel');
const { response } = require('../response/response');
const { TodoSchema } = require('../validators/todoValidator');
  • データベースとやり取りするTodoモデル

  • 一貫性のあるAPIレスポンスを送信するレスポンスハンドラ

  • TodoSchemaは Joiスキーマを使って入力データを検証します。

2. 新しいTodoを作成する: CreateTodo関数は、データベースに新しいTodoアイテムを作成します:

const CreateTodo = async (req, res) => {
    try {
        const { error, value } = TodoSchema.validate(req.body);
        if (error) {
            response.BadRequest(res, error.details[0].message);
        }
        const newTodo = await Todo.create({
            title: value?.title,
            description: value?.description,
            status: value?.status
        });
        response.Created(res, newTodo, "Todo created successfully");
    } catch (err) {
        response.InternalServerError(res, err.message);
    }
};

まず、TodoSchema.validateを使ってリクエストデータのバリデーションを行います。バリデーションに失敗した場合は、400 Bad Requestを説明メッセージとともに返します。成功した場合は、Todo.createメソッドを使って検証済みのデータをデータベースに挿入します。新しく作成された Todo アイテムと共に、201 Createdレスポンスが返されます。

Todo.findAll() を使ってすべてのレコードを取得します。取得に成功した場合は、Todo のリストとともに200 Successレスポンスを返します。

4. IDでTodoを取得 GetOneTodo関数は、一意のIDによってTodoを取得します。

const GetOneTodo = async (req, res) => {
    try {
        const todo = await Todo.findByPk(req.params.id);
        if (!todo) return response.NotFound(res, 'Todo Not found');
        response.SuccessData(res, todo, "Todo fetched successfully");
    } catch (err) {
        response.InternalServerError(res, err.message);
    }
};

Todo.findByPkは、主キー(id)でTodoを検索するために使用されます。Todo が見つからない場合は、404 Not Foundレスポンスが送信されます。そうでない場合は、200 SuccessレスポンスでTodoを返します。

5. IDでTodoを更新する UpdateOneTodoById関数は、既存のTodo項目を更新します。

const UpdateOneTodoById = async (req, res) => {
    try {
        const todo = await Todo.findByPk(req.params.id);
        if (!todo) return response.NotFound(res, 'Todo Not found');
        const { error, value } = TodoSchema.validate(req.body);
        if (error) {
            response.BadRequest(res, error.details[0].message);
        }
        const updatedTodo =await todo.update({
            title: value?.title,
            description: value?.description,
            status: value?.status
        });
        response.SuccessData(res, updatedTodo, "Todo updated successfully");
    } catch (err) {
        response.InternalServerError(res, err.message);
    }
};

まずは、req.params.idを持つTodoを探します。Todoが存在しない場合、404 Not Foundレスポンスが送信されます。成功した場合は、有効なフィールドだけが更新されるように受信データを検証し、todo.updateを使ってTodoを更新し、200 Successレスポンスを返します。

6. ID による Todo の削除: DeleteOneTodoById関数は、既存の Todo を削除します:

const DeleteOneTodoById = async (req, res) => {
    try {
        const todo = await Todo.findByPk(req.params.id);
        if (!todo) return response.NotFound(res, 'Todo Not found');
        await todo.destroy();
        response.Success(res, 'Todo deleted successfully');
    } catch (err) {
        response.InternalServerError(res, err.message);
    }
};

Todo.findByPkを使って ID から Todo を探します。Todo が見つかったら、todo.destroy() を使って削除します。削除を確認するために200 Successレスポンスを返します。

7. モジュールのエクスポート 最後に、これらの関数をすべてエクスポートする必要がある。そこで、以下のエクスポート・コード・スニペットをファイルの最後に追加します。

module.exports = { CreateTodo, GetAllTodos, GetOneTodo, UpdateOneTodoById, DeleteOneTodoById };

最終的なtodoControllersのコードベースはこのようになる:

const Todo = require('../models/todoModel');
const { response } = require('../response/response');
const { TodoSchema } = require('../validators/todoValidator');

// Create a New Todo
const CreateTodo = async (req, res) => {
    try {
        const { error, value } = TodoSchema.validate(req.body);
        if (error) {
            response.BadReqest(res, error.details[0].message)
        }
        const newTodo = await Todo.create({
            title: value?.title,
            description: value?.description,
            status: value?.status
        });
        response.Created(res, newTodo, "Todo created successfully")
    } catch (err) {
        response.InternalServerError(res, err.message)
    }
};

// Get All Todos
const GetAllTodos = async (req, res) => {
    try {
        const todos = await Todo.findAll();
        response.SuccessData(res, todos, "Todo fetched successfully")

    } catch (err) {
        response.InternalServerError(res, err.message)
    }
};

// Get Todo by ID
const GetOneTodo = async (req, res) => {
    try {
        const todo = await Todo.findByPk(req.params.id);
        if (!todo) return response.NotFound(res, 'Todo Not found')
        response.SuccessData(res, todo, "Todo fetched successfully")
    } catch (err) {
        response.InternalServerError(res, err.message)
    }
};


// Update a Todo
const UpdateOneTodoById = async (req, res) => {
    try {
        const todo = await Todo.findByPk(req.params.id);
        if (!todo) return response.NotFound(res, 'Todo Not found')
        const { error, value } = TodoSchema.validate(req.body);
        if (error) {
            response.BadReqest(res, error.details[0].message)
        }
        const updatedTodo = await todo.update({
            title: value?.title,
            description: value?.description,
            status: value?.status
        });
        response.SuccessData(res, updatedTodo, "Todo updated successfully")
    } catch (err) {
        response.InternalServerError(res, err.message)
    }
};


// Delete a Todo
const DeleteOneTodoById = async (req, res) => {
    try {
        const todo = await Todo.findByPk(req.params.id);
        if (!todo) return response.NotFound(res, 'Todo Not found')
        await todo.destroy();
        response.Success(res, 'Todo deleted successfully')
    } catch (err) {
        response.InternalServerError(res, err.message)
    }
};


module.exports = { CreateTodo, GetAllTodos, GetOneTodo, UpdateOneTodoById, DeleteOneTodoById };

ステップ8:Todoルートを作成する

routes/todoRoutes.jsにCRUD操作用のルートを作成する:

const express = require('express');
const router = express.Router();
const todoController = require('../controllers/todoController');
router.post('todo', todoController.CreateTodo);
router.get('todos', todoController.GetAllTodos);
router.get('todo/:id', todoController.GetOneTodo);
router.put('todo/:id', todoController.UpdateOneTodoById);
router.delete('todo/:id', todoController.DeleteOneTodoById);
module.exports = router;

ステップ9:メイン・アプリケーション・ファイルの作成

index.jsファイルは、アプリケーションのメイン・エントリ・ポイントとなります。このファイルは、Express サーバーのセットアップ、データベースへの接続、Todo API に必要なミドルウェアとルートの設定を行います。モデル、コントローラ、ルートなど、作成したすべてのコンポーネントを Node.js アプリケーションとして機能させるために、このファイルは非常に重要です。

const express = require('express');
const bodyParser = require('body-parser');
const dotenv = require('dotenv');
const todoRoutes = require('./routes/todoRoutes');
const sequelize = require('./config/db');

// Load environment variables
dotenv.config();

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

// Middleware
app.use(bodyParser.json());

// Use the routes
app.use('/api', todoRoutes);

// Sync the database
sequelize.sync().then(() => {
    console.log('Database & tables created!');
});


// Start the server
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

ステップ10: APIの実行とテスト

1.サーバーを起動します:

npm start

これで、指定したポートでサーバーが起動し、これら5つの異なるAPIエンドポイントを使用して完全なCRUDアプリケーションを実行できるようになる。
GET:http://localhost:5000/api/todos

GET: http://localhost:5000/api/todos/:id

POST: http://localhost:5000/api/todos

PUT: http://localhost:5000/api/todos/:id

DELETE: http://localhost:5000/api/todos/:id

結論:

このガイドに従うことで、Node.jsとExpress.jsサーバーのセットアップ、MySQLデータベースへの接続、基本的なCRUD操作の実装を実際に体験することができます。これらの概念を理解することは、より複雑なアプリケーションを作成したり、認証を統合したり、あるいは実運用用にプロジェクトを拡張したりするための強力な基礎となります。これらのスキルがあれば、さらなる可能性を追求し、新しい機能でアプリケーションを強化することができます。どんどん実験して、楽しくコーディングしましょう!🎉


協業開発及び開発パートナーをお探しのお客様へ

弊社は、ネパールに海外拠点を持ち、生成AI、モバイルアプリ、システム開発を中心に事業を展開する企業です。

自社サービスの開発経験を活かし、クライアント様と共に事業を創造することを重視し、創業以来、スタートアップから中小企業、大手企業、自治体まで、幅広い開発実績があります。プロダクトはユーザーが使いやすいように設計しており、企画から開発、保守運用まで対応しています。開発技術を厳選し限定することで、セキュリティ、プロダクトの品質向上に努めており、事業開発に関する課題を深く理解し、最適なご提案が可能です

お問い合わせはこちらから:
お問い合わせフォーム:https://readytowork.jp/

直通番号:080-8940-7169