Home
Blog
Products
Profile
Study
Collatz
© 2024 Oizumi Yuta

Next.js と AWS で ToDo アプリを作った

2025-2-3

ブログをさぼってしまった。毎日続けている人はすごいなあ。

Next.js と AWS で ToDo アプリを作ったので紹介する。

はじめに

Next.js × AWS で ToDo アプリを作った 🚀

https://todo.oizumi-yuta.com/

アカウント登録はメール認証を行っていないため、

test@examle.com
のようなダミーのメールアドレスで可能。

💻 フロントエンド

🔑 ログイン画面の実装

シンプルなログイン画面を作った。

image.png

工夫したところはパスワードマスクの解除。目のアイコンをクリックすると

type
を
text
にすることでマスクを解除できる。

<div className={styles.formGroup}>
  <label htmlFor="password">パスワード</label>
  <div className={styles.passwordContainer}>
    <input
      id="password"
      type={showPassword ? "text" : "password"} // textにすることでパスワードマスク解除
      value={password}
      onChange={(e) => setPassword(e.target.value)}
      required
      minLength={6}
      className={styles.input}
    />
    <span
      className={styles.passwordToggleIcon}
      onClick={() => setShowPassword(!showPassword)} // アイコンクリック時ステートを更新
    >
      {/* パスワードマスクアイコン切り替え */}
      {showPassword ? <PasswordEyeIcon /> : <PasswordCloseEyeIcon />}
    </span>
  </div>
</div>

✏️ ToDo 登録画面

ToDo 登録画面はサイドメニューに ToDo 一覧を表示し、右側にタスク詳細を表示するように実装した。

image.png

画面サイズが 950 px 以下になったとき、サイドメニューをアイコンで表示/非表示を切り替えることができるようにした。

image.png

アイコンは以下を利用。

https://fonts.google.com/icons

画面サイズの変更イベントをリッスンするために

useEffect
で
addEventListener
を行う。

useEffect(() => {
  if (userInfo.userId) {
    getTodos();
  }

  // 画面サイズのステートを更新する関数
  const handleResize = () => {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight,
    });
  };

  handleResize();

  // イベントリスナーに追加
  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, [userInfo.userId]);

参考: https://ja.react.dev/learn/separating-events-from-effects

メニューアイコンをクリックするとオーバーレイを施す。

image.png

ToDo チェックボックスをおしゃれにする

To-Do のチェックボックスをおしゃれにした。

image.png

参考: https://getcssscan.com/css-checkboxes-examples

👉 こちらのサイトはとても便利でおしゃれな CSS の例が載っています。

ローディングアイコンをおしゃれにする

ローディングアイコンもおしゃれにした。

image.png

https://cssloaders.github.io/

👉 こちらのサイトは豊富なローディングアイコンの例が載っている。

⚡ バックエンド

バックエンドはコスト削減のためサーバーレス構成にした。

DynamoDB キー設計

image.png

属性名型理由
userIdSユーザー ID(UUID)
todoIdSToDo ID(UUID)
titleSToDo タイトル
descriptionSToDo 説明
statusN0: 未完了、1: 完了
createdAtS作成日時(ISO8601 JST)
updatedAtS更新日時(ISO8601 JST)

Lambda 関数による DynamoDB CRUD 操作

SDK for JavaScript を用いたデータ登録・更新・削除を実装した。

  • 共通設定
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  QueryCommand,
  ScanCommand,
  UpdateCommand,
  DeleteCommand,
} from '@aws-sdk/lib-dynamodb';

// DynamoDBクライアント設定
const client = new DynamoDBClient({ region: process.env.AWS_REGION });
const dynamoDb = DynamoDBDocumentClient.from(client);

// DynamoDBテーブル
const TABLE_NAME = 'next-todo-app';
const USER_TABLE_NAME = 'next-todo-app-users';

登録、更新、削除は以下にコード例がある。

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v3/developer-guide/javascript_dynamodb_code_examples.html

https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javascriptv3/example_code/dynamodb#code-examples

ToDo 登録

const newItem = {
  userId,
  todoId,
  title,
  description: description || '',
  status,
  createdAt: timestamp,
  updatedAt: timestamp,
};

const params = {
  TableName: TABLE_NAME,
  Item: newItem,
};

await dynamoDb.send(new PutCommand(params));

ToDo 更新

const params = {
  TableName: TABLE_NAME,
  Key: {
    userId: userId,
    todoId: todoId,
  },
  UpdateExpression:
    'set #title = :title, #description = :description, #status = :status, #updatedAt = :updatedAt',
  ExpressionAttributeNames: {
    '#title': 'title',
    '#description': 'description',
    '#status': 'status',
    '#updatedAt': 'updatedAt',
  },
  ExpressionAttributeValues: {
    ':title': title,
    ':description': description,
    ':status': status,
    ':updatedAt': updatedAt,
  },
  ReturnValues: 'ALL_NEW',
};

const result = await dynamoDb.send(new UpdateCommand(params));
res.json(result.Attributes);

ToDo 削除

const params = {
  TableName: TABLE_NAME,
  Key: {
    userId: userId,
    todoId: todoId,
  },
};

await dynamoDb.send(new DeleteCommand(params));

API Gateway

リソースはルートパスの直下に

{proxy+}
で作成する。 Lambda プロキシ統合を True にする。URL パスパラメータは設定していないが、勝手に追加されていた。

image.png

CORS 設定とデプロイを忘れがちなので注意が必要。

最後に

個人開発はハマると楽しい。しかしアイデアが浮かばなかったりモチベーション維持が難しい。そんなときに無理にひねり出そうとすると結局何もやらないことになってしまいがちだ。私もしばらく何も手がつかなかった。そんなときは初心に帰ってシンプルなアプリを作ってみると案外ハマることに気づいた。