ExpressとMongoDBで簡単にWeb APIを構築する方法【TypeScript対応】

2024/12/09に公開

はじめに

近年、クラウドデータベースと型安全なプログラミング言語を活用したWebアプリケーション開発が注目を集めています。特に、MongoDB Atlasは手軽にデータベースをクラウドで管理でき、スケーラブルなアプリケーション構築に適しています。また、TypeScriptはコードの可読性と保守性を高めるため、多くの開発者に採用されています。

本記事では、これらの技術を組み合わせて、REST APIを構築する方法を具体的に解説します。MongoDB AtlasのセットアップからExpressを使ったルーティング、TypeScriptを用いた型安全な実装方法まで、初心者の方にも分かりやすいステップバイステップ形式で進めていきます。この記事を読むことで、MongoDB Atlasを活用したAPI開発の基礎を身につけるとともに、型安全な開発手法の利点を実感していただけるでしょう。

Expressの仕組みがまだあまり詳しくないという方向けにExpressの基本的な使い方を解説した記事をご用意しています。

https://shinagawa-web.com/blogs/express-typescript-setup-guide

MongoDBとは

MongoDBは、ドキュメント指向のNoSQLデータベースで、柔軟なデータモデルやスケーラビリティに優れたデータベース管理システムです。以下の特徴を持っています:

主な特徴

  1. ドキュメント指向
    データはJSON形式に似た「BSON(Binary JSON)」として保存されます。テーブルや行(RDBMS)ではなく、コレクション(Collection)とドキュメント(Document)という概念を用います。

  2. スキーマレス
    明確なスキーマ定義が不要で、データ構造を柔軟に変更可能。プロジェクトの要件変更やスケールアップに強い。

  3. 高いスケーラビリティ
    シャーディング(データの分割)により、水平スケーリングが簡単。大量のデータを効率よく処理可能。

  4. 高性能
    高速な読み書き性能。特にリアルタイムデータの分析や、頻繁にデータ構造が変わるアプリケーションに最適。

  5. 豊富なエコシステム
    Node.js、Python、Javaなどの主要なプログラミング言語用ドライバを提供。MongoDB Atlas(クラウドサービス)を使えば、インフラ構築なしでデータベースを利用可能。

MongoDB Atlasとは

MongoDB Atlasは、MongoDB社が提供するクラウドベースのデータベースサービスです。手間のかかるサーバー構築や運用管理を代行してくれるフルマネージド型のデータベースプラットフォームで、以下のクラウドプロバイダーで利用できます:

  • AWS(Amazon Web Services)
  • GCP(Google Cloud Platform)
  • Microsoft Azure

ローカル環境でDockerを起動してMongoDBをセットアップする選択肢もありますがより簡単に始められるということで今回はMongoDB Atlasを使っていきます。

MongoDB Atlasはストレージが512MBまでであれば無料で使えます。

mongoDB Atlasの設定

こちらからアクセスしてアカウント登録を行います。

https://www.mongodb.com/cloud/atlas/register

New Projectで新規プロジェクト作成画面へ

Image from Gyazo

プロジェクト名はexpress-tutorialとします。

Image from Gyazo

Create Projectでプロジェクトを作成します。

Image from Gyazo

プロジェクトの作成が完了しましたので次はクラスターの作成を行います。

Image from Gyazo

クラスターのスペックを選択できます。

今回はFreeとなっているM0を選択し、クラスターを作成します。

Image from Gyazo

クラスターに接続するIPアドレスとユーザーを設定します。

IPアドレスについては一旦current IP Addressでいいかと思います。Expressをどこかにデプロイする際には設定を変える必要が出てきます。

後はユーザーとパスワードを設定します。

Image from Gyazo

設定が完了するとChoose a connection method

Image from Gyazo

Driversを選択

Image from Gyazo

Mongooseを選択

Image from Gyazo

環境変数に設定する必要があるためメモしておきます。

mongodb+srv://dbUser:<db_password>@cluster0.elo46.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0

プロジェクトのセットアップ

Express用のプロジェクトを新規作成し必要なパッケージをインストールします。

mkdir express-mongodb-rest-api-development-with-typescript
cd express-mongodb-rest-api-development-with-typescript
npm init -y

パッケージのインストール

npm i express cors dotenv express-async-handler mongoose
npm i -D @types/node @types/express @types/mongoose @types/cors rimraf @types/rimraf ts-node-dev typescript

express

  • 用途: Node.js のウェブフレームワーク。
  • 役割:
    • ルーティング(HTTPリクエストの処理を分岐)。
    • ミドルウェアを使ったリクエスト/レスポンスの処理。
    • REST APIやWebアプリケーションの構築に便利。

@types/express

  • 用途: Expressの型定義ファイル。
  • 役割:
    • TypeScriptでExpressを型安全に使用可能にする。
    • ルートやミドルウェアの引数・戻り値の型を明確化。

cors

  • 用途: クロスオリジンリソース共有(CORS)を設定するミドルウェア。
  • 役割:
    • 他のオリジン(ドメイン)からのリクエストを許可。
    • フロントエンドとバックエンドの安全な連携を実現。

@types/cors

  • 用途: CORSの型定義ファイル。
  • 役割:
    • TypeScriptでCORSミドルウェアを型安全に使用可能にする。
    • 設定オプションの型を明示。

dotenv

  • 用途: .env ファイルから環境変数を読み込むパッケージ。
  • 役割:
    • 環境変数(APIキーやデータベースURLなど)を安全に管理。
    • 環境ごとの設定切り替えを簡素化。

express-async-handler

  • 用途: Expressの非同期関数を簡単に扱うためのユーティリティ。
  • 役割:
    • 非同期関数内のエラー処理を簡素化。
    • try-catch を書かずにエラーハンドラーにエラーを渡す。

mongoose

  • 用途: MongoDB用のオブジェクトデータモデリング(ODM)ライブラリ。
  • 役割:
    • スキーマやモデルを定義。
    • MongoDBとのクエリを簡素化。
    • ドキュメントとJavaScriptオブジェクトのマッピングを提供。

@types/mongoose

  • 用途: Mongooseの型定義ファイル。
  • 役割:
    • TypeScriptでMongooseを型安全に使用可能にする。
    • モデルやスキーマの型定義を強化。

@types/node

  • 用途: Node.jsの型定義ファイル。
  • 役割:
    • TypeScriptでNode.jsの組み込みモジュール(fs, http, pathなど)を型安全に使用可能にする。

rimraf

  • 用途: フォルダやファイルを削除するためのユーティリティ。
  • 役割:
    • クロスプラットフォームでrm -rfのような操作を実現。

@types/rimraf

  • 用途: rimrafの型定義ファイル。
  • 役割:
    • TypeScriptでrimrafを型安全に使用可能にする。

ts-node

  • 用途: TypeScriptコードを直接実行するためのツール。
  • 役割:
    • トランスパイルせずにTypeScriptのスクリプトを実行。
    • 開発中に便利。

ts-node-dev

  • 用途: TypeScriptコードのホットリロード用ツール。
  • 役割:
    • コード変更時にサーバーを自動再起動。
    • 開発の効率化。

tsconfigの作成

TypeScript プロジェクトのコンパイラ設定を管理するためのtsconfig.jsonファイルを作成します。

npx tsc --init

.envの作成

先ほど取得したMongoDBへの接続情報を.envで管理します。

MONGO_URI=mongodb+srv://dbUser:dbUserPassword@cluster0.elo46.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0

ExpressからMongoDBへの接続

MongoDBへの接続処理

src/lib/db.tsファイルを作成しコードを書いていきます。

src/lib/db.ts
import mongoose from 'mongoose';
import dotenv from 'dotenv';

dotenv.config();

const DATABASE_URL = process.env.MONGO_URI!

const connectDB = async () => {
  try {
    const connection = await mongoose.connect(DATABASE_URL);
    console.log(`MongoDB Connected ${connection.connection.host}`);
  } catch (error) {
    if (error instanceof Error) {
      console.error(`Error: ${error.message}`);
    } else {
      console.error(`Unexpected Error: ${error}`);
    }
    process.exit(1);
  }
};

export default connectDB;

Expressのエントリポイント作成

src/index.ts
import express from 'express';
import cors from 'cors';
import connectDB from './lib/db';

connectDB();

const app = express();

app.use(cors());

app.use(express.json());
app.use(express.urlencoded({ extended: true }));


app.listen(3001);
console.log('Express WebAPI listening on port ' + port);

package.jsonの設定

Expressサーバーを起動するためのコマンドをpackage.jsonに設定します。

package.json
  "scripts": {
+   "dev": "ts-node-dev --respawn src/index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

動作確認

下記のコマンドでExpressサーバの起動とMongoDBへの接続を行います。

npm run dev

下記のようなメッセージが表示されましたらどちらも成功となります。

Image from Gyazo

Modelの作成

MongoDBにデータを格納するためのデータの定義を行っていきます。
今回はTODOアプリを作成する想定で以下の項目を用意します。

  • タイトル:String
  • 完了フラグ:Boolean (初期値:false)
  • 作成日:Date (初期値:登録日時)
  • 更新日:Date
src/models/todo.ts
import mongoose from 'mongoose';

const PostSchema = new mongoose.Schema(
  {
    title: {
      type: String,
      required: true,
    },
    isCompleted: {
      type: Boolean,
      required: true,
      default: false,
    },
    createdAt: {
      type: Date,
      required: true,
      default: Date.now,
    },
    updatedAt: {
      type: Date,
    },
  },
);

const Post = mongoose.model('Post', PostSchema);

export default Post;

データの登録

先ほど作成したモデルを使って初期データをローカル環境からMongoDBへ登録します。

登録用のデータ作成

2件todoを作成します。

isCompletedcreatedAtについてはデフォルト値を設定しているためここでは省略しています。

src/seeds/todos.ts
export const todos = [
  {
    title: '問い合わせに返信する',
  },
  {
    title: 'ブログを作成する',
  },
]

登録処理スクリプトの作成

登録する処理についてはやり直しが効くように一度全件削除してから登録します。

また全件削除ができる処理も事前に用意しています。

src/seeds/seed.ts
import dotenv from 'dotenv';
import { todos } from './todo';
import Todo from '../models/todo';
import connectDB from '../lib/db';

dotenv.config();

connectDB();

const importData = async () => {
  try {
    // todoを一旦削除してから全件登録
    await Todo.deleteMany();
    const createPosts = await Todo.insertMany(todos);

    console.log(`Data Imported!`);
    process.exit();
  } catch (error) {
    console.error(`Error: ${error}`);
    process.exit(1);
  }
};

const destroyData = async () => {
  try {
    await Todo.deleteMany();

    console.log(`Data Destroyed!`);
    process.exit();
  } catch (error) {
    console.error(`Error: ${error}`);
    process.exit(1);
  }
};

// コマンドラインでパラメータ -d を渡すと削除モードにする
if (process.argv[2] === '-d') {
  destroyData();
} else {
  importData();
}

データの登録

下記コマンドでデータの登録を行います。

npx ts-node src/seeds/seed.ts

Data Imported!と表示されれば登録完了です。

Image from Gyazo

MongoDB Atlas上でも登録されたことを確認できます。

Image from Gyazo

一通りの準備ができましたのでここから実装に入っていきます。

データの参照(GETメソッド)

まずは登録済みのデータを参照するGETメソッドを実装します。

コントローラーの作成

コントローラーにDBアクセスの処理を作成します。

src/controllers/todo.ts
import {Request, Response} from 'express';
import asyncHandler from 'express-async-handler';
import Todo from '../models/todo';

export const getPosts = asyncHandler(async (req: Request, res: Response) => {
  const todos = await Todo.find();
  res.json(todos);
});

ルーティングの作成

GETメソッドでアクセスが来た場合に先ほどのgetTodosを呼び出すよう定義します。
またこの後、POSTメソッドなど他のルートも作成するためグループ化しておきます。

src/routes/todo.ts
import express from 'express';
import { getTodos } from '../controllers/todo';

const router = express.Router();

router.route('/').get(getTodos)

export default router

エントリポイントにルーティングの反映

上記内容をエントリーポイントに反映させます。
今回のTODOアプリのURLは/todos配下に全て設定されます。

src/index.ts
import express from 'express';
import cors from 'cors';
import connectDB from './lib/db';
+ import todoRoutes from './routes/todo'

connectDB();

const app = express();

app.use(cors());

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

+ app.use('/todos', todoRoutes)

app.listen(3001);
console.log('Express WebAPI listening on port 3001');

Expressのルーティングについては下記の記事で詳しく説明してあります。

https://shinagawa-web.com/blogs/express-typescript-setup-guide#ルーティング

動作確認

実際にAPIにアクセスしてデータが参照できるか確認します。
出力結果を見やすくするためにjqを使用しています。

curl -s http://localhost:3001/todos | jq

事前に登録した2件のtodoが出力されればOKです。

Image from Gyazo

データの新規登録(POSTメソッド)

GETメソッドと実装の仕方はほぼ同じです。

src/controllers/todo.ts
export const addTodo = asyncHandler(async (req: express.Request, res: express.Response) => {
  const newPost = new Post(req.body as PostType);

  await newPost.save((error, post) => {
    if (error) res.send(error);
    res.json(post);
  });
});

POSTメソッドのアクセスURLはGETメソッドと同じものを使用します。

src/routes/todo.ts
- router.route('/').get(getTodos)
+ router.route('/').get(getTodos).post(addTodo)

動作確認

POSTメソッドでリクエストを送信します。

curl -X POST -H "Content-Type: application/json" -d '{"title": "投稿テスト"}' http://localhost:3001/todos

正常に登録されますと登録内容が返却されます。

Image from Gyazo

データの更新(PATCHメソッド)

受け取ったtodoIdを元に対象のデータを探して更新します。
idが存在せず更新できなかった場合はその旨をメッセージとして返します。

src/controllers/todo.ts
export const updateTodo = asyncHandler(async (req: Request, res: Response): Promise<void> => {
  const todo = await Todo.findByIdAndUpdate(
    { _id: req.params.todoId },
    req.body,
    { new: true }
  );

  if (!todo) {
    res.status(404).json({ message: "Todo not found" });
  }

  res.json(todo)
});

todoIdを受け取れるようルーティングを定義します。

src/routes/todo.ts
router.route('/:todoId').patch(updateTodo)

動作確認

今回は登録済みのTodoに対して完了したフラグをつけてみます。

更新対象のTodoはコチラにします。

[
  {
    "_id": "678064b4923d0c1c0e9ebea6",
    "title": "問い合わせに返信する",
    "isCompleted": false,
    "createdAt": "2025-01-10T00:07:16.292Z",
    "__v": 0
  },

{"isCompleted": true}をPATCHメソッドのbodyにセット。
更新対象のIDはURLにセットします。

curl -X PATCH -H "Content-Type: application/json" -d '{"isCompleted": true}' http://localhost:3001/todos/678064b4923d0c1c0e9ebea6

更新結果でisCompleted: trueとなったことを確認できました。

Image from Gyazo

id678064b4923d0c1c0e9ebea5と、一番最後の数字を変更し存在しないidにした状態でリクエストを送ると、設定したメッセージが返ってくることが確認できます。

Image from Gyazo

データの削除(DELETEメソッド)

受け取ったtodoIdを元に対象のデータを探して削除します。
idが存在せず削除できなかった場合はその旨をメッセージとして返します。

src/controllers/todo.ts
export const deleteTodo = asyncHandler(async (req: Request, res: Response): Promise<void> => {

  const todo = await Todo.findByIdAndDelete(req.params.todoId);

  if (!todo) {
    res.status(404).json({ message: "Todo not found" });
  }

  res.json({ message: "Todo deleted successfully", todo });
});

DELETEメソッドもtodoIdを受け取れるようルーティングを定義します。

src/routes/todo.ts
- router.route('/:todoId').patch(updateTodo)
+ router.route('/:todoId').patch(updateTodo).delete(deleteTodo)

動作確認

こちらのデータを削除します。

  {
    "_id": "6780847655833897d0f2f103",
    "title": "投稿テスト",
    "isCompleted": false,
    "createdAt": "2025-01-10T02:22:46.525Z",
    "__v": 0
  }
]

下記のコマンドでDELETEメソッドのリクエストを送信します。

curl -X DELETE http://localhost:3001/todos/6780847655833897d0f2f103

正常に削除されることが確認できるかと思います。

Image from Gyazo

一度実行した後に再度同じリクエストを送信すると既に存在しないidとなっているため削除できない旨のメッセージが表示されます。

Image from Gyazo

さいごに

本記事では、MongoDB Atlasを活用したREST APIの構築方法について、初期設定から実装、そして型安全な開発のポイントまでを解説しました。これを通じて、MongoDB AtlasとTypeScriptの基本的な活用方法をご理解いただけたのではないでしょうか。

これからアプリケーションをさらに拡張する際には、認証機能や高度なエラーハンドリング、そしてデプロイ環境の整備なども視野に入れることで、より実用的で信頼性の高いAPIを作成できます。また、TypeScriptの型付けを強化することで、チーム開発や長期的なプロジェクトにおけるメンテナンス性も向上します。

MongoDB AtlasとTypeScriptを活用しプロジェクトが成功することを応援しています。

おすすめ記事

Supertest と Jest を組み合わせて Express アプリケーションの API テストを効率化する方法を解説します。
サービス層の導入やテスト可能なコード設計へのリファクタリング、GET, POST, PATCH, DELETEメソッドのテスト実装まで、具体的なコード例を交えて詳しく紹介します。

https://shinagawa-web.com/blogs/supertest-jest-express-mongodb-end-to-end-testing

記事に関するお問い合わせ📝

記事の内容に関するご質問、ご意見などは、下記よりお気軽にお問い合わせください。
ご質問フォームへ

技術支援などお仕事に関するお問い合わせ📄

技術支援やお仕事のご依頼に関するお問い合わせは、下記よりお気軽にお問い合わせください。
お問い合わせフォームへ