tanaka101

勤務記録を返す

モックデータを準備し、GET エンドポイントで勤務記録を返します。

このレッスンで作るもの

勤怠管理APIの最初のを作ります。

  • GET /records - 勤務記録の一覧を取得
  • GET /records/:id - 特定の勤務記録を取得

上記のを叩いたときに、勤務記録を取得できる状態がこのレッスンのゴールです。

Step 1: データの型を定義する

まず、勤務記録のデータ構造を の型で定義します。

src/types.ts を作成してください。

src/types.ts
export interface AttendanceRecord {
  id: string;
  employeeName: string;
  date: string;          // "2026-01-28"
  clockIn: string;       // "09:00"
  clockOut: string | null;
  note: string | null;   // 備考(遅刻理由など)
}

型定義のポイント

プロパティ説明
idstring各記録を一意に識別するID
employeeNamestring従業員名
datestring勤務日(YYYY-MM-DD形式)
clockInstring出勤時刻(HH:mm形式)
clockOutstring | null退勤時刻(未退勤ならnull)
notestring | null備考(なければnull)

オプショナル(?)とパイプ(|)の使い分け

clockOut: string | null は「値は必ず存在するが、中身が null の場合がある」 ことを表します。DBのカラムのように、フィールド自体は常にあるけれど 「まだ退勤していない」なら null が入る、というケースです。

一方、note?: string のようにオプショナル(?)で書くと、 「そのプロパティ自体を省略できる」という意味になります。 の引数で「指定しなくてもいい」場合に使うことが多いです。

今回の AttendanceRecord では、全フィールドがDBのカラムとして 必ず存在するため、「あるかないか分からない」オプショナルではなく、 string | null を使っています。

Step 2: を作成する

実際のデータベースの代わりに、配列でデータを管理します。

src/data.ts を作成してください。
軽く目を通して意味が分かったらコピペしていただいて構いません。

src/data.ts
import { AttendanceRecord } from './types.js';
 
export const records: AttendanceRecord[] = [
  {
    id: '1',
    employeeName: '田中太郎',
    date: '2026-01-27',
    clockIn: '09:00',
    clockOut: '18:00',
    note: null,
  },
  {
    id: '2',
    employeeName: '佐藤花子',
    date: '2026-01-27',
    clockIn: '08:45',
    clockOut: '17:30',
    note: null,
  },
  {
    id: '3',
    employeeName: '田中太郎',
    date: '2026-01-28',
    clockIn: '09:15',
    clockOut: null,
    note: '電車遅延',
  },
];

このファイルの役割はの定義のみです。データへのアクセスロジック(検索や取得)は次のステップで別ファイルに書きます。

なぜデータと処理を分けるのか?

data.tsと取得をまとめて書くこともできますが、「データの定義」と「データへのアクセスロジック」はそれぞれ異なる役割です。

たとえば、将来をデータベースに置き換える場合、data.tsごと不要になります。一方、アクセスロジックは中身がDB呼び出しに変わるだけで、ファイル自体は残ります。役割ごとにファイルを分けておけば、変更の影響範囲が明確になり、修正がしやすくなります。

Step 3: データアクセスを作成する

に対する検索・取得のロジックを src/record-repository.ts に書きます。

src/record-repository.ts を作成してください。

src/record-repository.ts
import { AttendanceRecord } from './types.js';
import { records } from './data.js';
 
export function getRecords(): AttendanceRecord[] {
  return records;
}
 
export function getRecordById(id: string): AttendanceRecord | undefined {
  return records.find((record) => record.id === id);
}

コードの解説

このファイルでは、勤務記録の取得に必要なを定義しています。

役割
getRecords()全ての勤務記録を返す
getRecordById(id)IDで1件の記録を検索して返す

getRecordByIdだけ戻り値にundefinedがある理由

getRecordsは、サーバーが持っている全データをそのまま返すだけなので、 結果は常にAttendanceRecord[](配列)です。空の配列になることはあっても、 データ自体が「見つからない」という状況は起きません。

一方、getRecordByIdは、クライアントから渡されたIDで1件を検索します。 データが100件しかないのに id: "9999" を指定された場合、該当するデータは 存在しないため undefined を返します。

このように外部からの入力によって「見つからない」ケースがあるは、 戻り値に undefined を含めて、呼び出し側に「見つからない可能性がある」 ことを型で伝えます。

Step 4: 一覧取得APIを作る

src/index.ts を修正して、GET /records を追加します。

src/index.ts
import express from 'express';
import { getRecords, getRecordById } from './record-repository.js';
 
const app = express();
 
app.use(express.json());
 
const PORT = 3000;
 
// 勤務記録の一覧を取得
app.get('/records', (req, res) => {
  const records = getRecords();
  res.json(records);
});
 
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

recordsというパスかつ、GETである場合は、このappオブジェクトがgetRecordsを呼び出します。
index.tsは処理を行うためのルーティング(に応じて呼び出すを選別する)を行っていて、具体的な処理は下位のが担っています。 (今回ならgetRecordsが下位) このような構造にすることで、APIの追加やロジックの保守が容易になります。

Step 5: ブラウザで確認する

開発サーバーを起動して、ブラウザで下記のURLにアクセスしてください。

http://localhost:3000/records

がJSON形式で表示されれば成功です。

getリクエストのレスポンスjson

Step 6: 個別取得APIを作る

特定のIDの記録を取得する GET /records/:id を追加します。

src/index.
// 勤務記録の一覧を取得
app.get('/records', (req, res) => {
  const records = getRecords();
  res.json(records);
});
 
// 特定の勤務記録を取得
app.get('/records/:id', (req, res) => {
  const record = getRecordById(req.params.id);
 
  if (!record) {
    res.status(404).json({ error: '記録が見つかりません' });
    return;
  }
 
  res.json(record);
});

パスパラメータ

:id の部分は パスパラメータ と呼ばれ、URLの一部を変数として受け取れます。

Expressの公式ドキュメントでは「ルートパラメータ(Route parameters)」と呼んでいます。意味は同じですが、パスパラメータの方がを問わず広く使われる用語です。

GET /records/1  →  req.params.id は "1"
GET /records/2  →  req.params.id は "2"

res.status(404)を設定しています。

コード意味使い場面
200OK(成功)正常にデータを返すとき(デフォルト)
404Not Foundデータが見つからないとき
400Bad に問題があるとき
500Internal Server Errorサーバー内部でエラーが起きたとき

を指定しない場合、Expressは自動的に 200 を返します。エラー時には適切なを明示的に設定しましょう。

Step 7: 動作確認

ブラウザで下記URLにアクセスしてみましょう。

http://localhost:3000/records/1

田中太郎の記録が返ってきます。

idを指定したGETリクエストのレスポンスjson

存在しないIDにアクセスしてみましょう。

http://localhost:3000/records/999

エラーレスポンスが返ってきます。

存在しないIDを指定したGETリクエストのレスポンスjson

完成コード

src/types.ts
export interface AttendanceRecord {
  id: string;
  employeeName: string;
  date: string;          // "2026-01-28"
  clockIn: string;       // "09:00"
  clockOut: string | null;
  note: string | null;   // 備考(遅刻理由など)
}
src/data.ts
import { AttendanceRecord } from './types.js';
 
export const records: AttendanceRecord[] = [
  {
    id: '1',
    employeeName: '田中太郎',
    date: '2026-01-27',
    clockIn: '09:00',
    clockOut: '18:00',
    note: null,
  },
  {
    id: '2',
    employeeName: '佐藤花子',
    date: '2026-01-27',
    clockIn: '08:45',
    clockOut: '17:30',
    note: null,
  },
  {
    id: '3',
    employeeName: '田中太郎',
    date: '2026-01-28',
    clockIn: '09:15',
    clockOut: null,
    note: '電車遅延',
  },
];
src/record-repository.ts
import { AttendanceRecord } from './types.js';
import { records } from './data.js';
 
export function getRecords(): AttendanceRecord[] {
  return records;
}
 
export function getRecordById(id: string): AttendanceRecord | undefined {
  return records.find((record) => record.id === id);
}
src/index.ts
import express from 'express';
import { getRecords, getRecordById } from './record-repository.js';
 
const app = express();
 
app.use(express.json());
 
const PORT = 3000;
 
// 勤務記録の一覧を取得
app.get('/records', (req, res) => {
  const records = getRecords();
  res.json(records);
});
 
// 特定の勤務記録を取得
app.get('/records/:id', (req, res) => {
  const record = getRecordById(req.params.id);
 
  if (!record) {
    res.status(404).json({ error: '記録が見つかりません' });
    return;
  }
 
  res.json(record);
});
 
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

現在のフォルダ構造

      • data.ts← 作成するファイル
      • index.ts← 作成するファイル
      • record-repository.ts← 作成するファイル
      • types.ts← 作成するファイル
    • package.json
    • package-lock.json
    • tsconfig.json

次のステップ

GETが完成しました。データの読み取りはできるようになりましたが、まだ新しいデータを作成することはできません。

次のレッスンでは、POSTで出勤・退勤の打刻を記録する機能を作ります。