はじめに
この記事では、Node.js / TypeScript / MySQL を使ってユーザーの CRUD 機能を実装する手順について記載します。
開発環境
開発環境は以下の通りです。
- Windows11
- VSCode
- Talend API Tester
- Docker Engine 27.0.3
- Docker Compose 2
- MySQL 9.0.0
- Node.js 20.15.1
- npm 10.8.2
- @types/node 20.14.11
- ts-node 10.9.2
- TypeScript 5.5.3
開発環境構築
以下の手順で開発環境を構築します。
実装方針
src/index.ts
でルーティングを行い、src/users.ts
で CRUD 処理を行います。
各 CRUD 処理は以下の流れで行います。
- DB接続
- CRUD クエリ実行
- DB接続切断
- レスポンス作成
Read
Read 機能を実装します。Read 機能は、全件取得と一件取得の2種類実装します。
全件取得
- HTTP リクエストメソッド :
GET
- エンドポイント : http://localhost:3000/users
src/users.ts
で全ユーザー取得処理を行います。
他の機能でも利用する型定義とエラー処理も記載します。
import { ServerResponse } from "http";
import { createConnection } from "./database";
import { RowDataPacket } from "mysql2";
type User = RowDataPacket & {
id: string;
name: string;
email: string;
};
const handleError = (res: ServerResponse, error: unknown) => {
console.error("Error: ", error);
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Internal Server Error" }));
};
export const getUsers = async (res: ServerResponse) => {
try {
const connection = await createConnection();
const [rows] = await connection.query<User[]>("SELECT * FROM users");
await connection.end();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(rows));
} catch (error) {
handleError(res, error);
}
};
src/users.ts
で実装した getUsers
を src/index.ts
で呼び出します。HTTP リクエストメソッドの種類で処理を振り分けます。
import { createServer, IncomingMessage, ServerResponse } from "http";
import { createConnection } from "./database";
import { getUsers } from "./users";
const handleRequest = async (req: IncomingMessage, res: ServerResponse) => {
const [_, resource] = req.url?.split("/") || [];
if (resource !== "users") {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ massage: "Not Found" }));
return;
}
switch (req.method) {
case "GET":
await getUsers(res);
break;
default:
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ massage: "Not Found" }));
}
};
const server = createServer(handleRequest);
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
createConnection();
});
動作確認をします。
初期データとしてDBに登録していた2件のユーザー情報が取得できます。
一件取得
- HTTP リクエストメソッド :
GET
- エンドポイント : http://localhost:3000/users/[:id]
一件取得の場合は、指定された ID のユーザーの有無による処理の分岐も行います。
...
export const getUserById = async (id: number, res: ServerResponse) => {
try {
const connection = await createConnection();
const [rows] = await connection.query<User[]>(
"SELECT * FROM users WHERE id = ?",
[id]
);
connection.end();
const user = rows[0];
if (!user) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "User not found" }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(user));
} catch (error) {
handleError(res, error);
}
};
一件取得も GET
を利用するので、リクエストに ID が含まれるかどうかで呼び出す関数を選択します。
...
const handleRequest = async (req: IncomingMessage, res: ServerResponse) => {
const [_, resource, resourceId] = req.url?.split("/") || [];
if (resource !== "users") {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ massage: "Not Found" }));
return;
}
const DECIMAL_RADIX = 10; // Named constant for the radix
const id = resourceId ? parseInt(resourceId, DECIMAL_RADIX) : null;
switch (req.method) {
case "GET":
id ? await getUserById(id, res) : await getUsers(res);
break;
default:
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ massage: "Not Found" }));
}
};
...
動作確認をします。
ID が 1 のユーザー情報が取得できます。
存在しないユーザー ID を指定すると、404 エラーになります。
Create
- HTTP リクエストメソッド :
POST
- エンドポイント : http://localhost:3000/users
- リクエストボディ :
{
"name": string;
"email": string;
}
リクエストボディから "name"
と email
を取り出す関数を追加します。この関数を使って取得した "name"
と email
をユーザーを作成するクエリ実行時に引数として渡します。
...
const parseBody = (req: IncomingMessage): Promise<Omit<User, "id">> => {
return new Promise((resolve, reject) => {
let body = "";
req.on("data", (chunk) => {
// Convert the chunk to a string and append it to the body
body += chunk.toString();
});
req.on("end", () => {
try {
// Parse the accumulated body as JSON
const parseBody: Omit<User, "id"> = JSON.parse(body);
resolve(parseBody);
} catch (error) {
// Reject the promise if parsing fails
reject(error);
}
});
});
};
...
export const createUser = async (req: IncomingMessage, res: ServerResponse) => {
try {
const user: Omit<User, "id"> = await parseBody(req);
const connection = await createConnection();
await connection.query("INSERT INTO users (name, email) VALUES (?, ?)", [
user.name,
user.email,
]);
connection.end();
res.writeHead(201, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "User created" }));
} catch (error) {
handleError(res, error);
}
};
HTTP リクエストメソッドが POST
の時、createUser
を実行します。
...
const handleRequest = async (req: IncomingMessage, res: ServerResponse) => {
...
switch (req.method) {
case "GET":
id ? await getUserById(id, res) : await getUsers(res);
break;
case "POST":
await createUser(req, res);
break;
default:
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ massage: "Not Found" }));
}
};
...
動作確認をします。
成功すると、201
と "User created"
が返ってきます。
また、全ユーザー取得をすると、先ほど作成したユーザーも含まれていることが確認できます。
Update
- HTTP リクエストメソッド :
PUT
- エンドポイント : http://localhost:3000/users/[:id]
- リクエストボディ :
{
"name"?: string;
"email"?: string;
}
更新クエリを実行する前に更新対象ユーザーが DB に存在するか確認します。存在しない場合、404 を返します。
存在する場合、Create 時と同じくリクエストボディから "name"
と email
を取り出し、クエリ実行時に引数として渡します。
...
export const updateUser = async (
id: number,
req: IncomingMessage,
res: ServerResponse
) => {
try {
const connection = await createConnection();
const [targetUsers] = await connection.query<User[]>(
"SELECT * FROM users WHERE id = ?",
[id]
);
const targetUser = targetUsers[0];
if (!targetUser) {
connection.end();
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "User not found" }));
return;
}
const user: Omit<User, "id"> = await parseBody(req);
await connection.query(
"UPDATE users SET name = ?, email = ? WHERE id = ?",
[user.name, user.email, id]
);
connection.end();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "User updated" }));
} catch (error) {
handleError(res, error);
}
};
HTTP リクエストメソッドが POST
の時、updateUser
を実行します。
const handleRequest = async (req: IncomingMessage, res: ServerResponse) => {
...
switch (req.method) {
case "GET":
id ? await getUserById(id, res) : await getUsers(res);
break;
case "POST":
await createUser(req, res);
break;
case "PUT":
id && (await updateUser(id, req, res));
break;
default:
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ massage: "Not Found" }));
}
};
動作確認をします。
成功すると、200
と "User updated"
が返ってきます。
対象ユーザーの情報を取得すると、"name" と "email" が更新されていることが確認できます。
なお、存在しないユーザーを更新しようとすると、404
エラーになります。
Delete
- HTTP リクエストメソッド :
DELETE
- エンドポイント : http://localhost:3000/users/[:id]
updateUser
と同じくクエリを実行する前に削除対象ユーザーが DB に存在するか確認します。存在しない場合、404 を返します。
存在する場合、クエリを実行します。
export const deleteUser = async (id: number, res: ServerResponse) => {
try {
const connection = await createConnection();
const [targetUsers] = await connection.query<User[]>(
"SELECT * FROM users WHERE id = ?",
[id]
);
const targetUser = targetUsers[0];
if (!targetUser) {
connection.end();
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "User not found" }));
return;
}
await connection.query("DELETE FROM users WHERE id = ?", [id]);
connection.end();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "User deleted" }));
} catch (error) {
handleError(res, error);
}
};
HTTP リクエストメソッドが DELETE
の時、deleteUser
を実行します。
const handleRequest = async (req: IncomingMessage, res: ServerResponse) => {
...
switch (req.method) {
case "GET":
id ? await getUserById(id, res) : await getUsers(res);
break;
case "POST":
await createUser(req, res);
break;
case "PUT":
id && (await updateUser(id, req, res));
break;
case "DELETE":
id && (await deleteUser(id, res));
break;
default:
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ massage: "Not Found" }));
}
};
...
動作確認をします。
成功すると、200
と "User deleted"
が返ってきます。
削除したユーザーの情報を取得しようとすると、404
エラーになります。
なお、存在しないユーザーを削除しようとした場合も 404
エラーになります。
参考
関連