リンクラフトでエンジニアとして働いている渡辺です。リンクラフトアドベントカレンダーの9日目を担当します。
さて、Next.js いいですね。バックからフロントまで一貫して書けるのでプロジェクトがコンパクトになりました。
ここしばらく Next.js を使う機会が多かったので、DB にアクセスしてそれを表示するまでを簡単に紹介してみようかと思います。
JavaScript で DB にアクセスしようとすると一番メジャーなのは Prisma でしょうか?ただ、RDB を対象にするとちょっと役者不足が否めません。 やっぱり SQL と同じように書きたい。というわけで TypeORM を使用します。
環境は Next.js+TypeScript+TypeORM です。
RDB および Node.js の環境は別途用意してください。今回は MySQL を使用します。
DB にアクセスできる設定を整えて、ページで DB にアクセスを行い画面に表示するまでを目標とします。
各バージョンは以下の通りです。
- Next.js v15
- TypeORM v0.3.20
- Node.js v22.11.0
- MySQL v8.3
プロジェクトの準備
まずプロジェクトを作成します。
npx create-next-app --typescript next-ts-typeorm
cd next-ts-typeorm
今回はcreate-next-app
の時import alias
だけYesにしています。
続けて必要なモジュールを追加します。
npm install typeorm reflect-metadata mysql dotenv
npm install -D ts-node tsx
tsx
はシーダー用です。シーダーが不要ならインストール不要です。
設定ファイル
各設定ファイルに設定を追加していきます。
tsconfig.json
のcompilerOptions
に以下を追加します。
"experimentalDecorators": true
package.json
に以下を追加します。
"type": "module",
同じくpackage.json
のscripts
に以下を追加します。
"typeorm": "typeorm-ts-node-esm -d ./src/models/datasources/migrationDataSource.ts",
"migration:generate": "npm run typeorm migration:generate --pretty src/models/migrations/${npm_config_name}",
"migration:run": "npm run typeorm migration:run",
"seeder": "npx tsx ${npm_config_src}",
npm run migration:generate --name=[マイグレーション名]
でマイグレーションファイルを作成し、npm run migration:run
でマイグレーションを実行できるようにします。
npm run seeder --src=[シーダーのファイル]
でシーダーを実行できるようにしますがオマケです。
.env
ファイルを作成し DB の設定を追加します。各々の環境にあったものを設定してください。
DATABASE_TYPE=mysql
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USERNAME=test_user
DATABASE_PASSWORD=hogehoge
DATABASE_NAME=test_db
DB まわりの設定
Entity を作成します。
この Entity がデータベースの設定そのものになり、データを取得する際の基本的な単位になります。
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id!: number;
@Column({
type: 'varchar',
length: 10,
})
name!: string;
@Column({
type: 'varchar',
length: 20,
})
address!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
}
続いて DataSource の作成を行います。
import { DataSourceOptions } from "typeorm";
export default function getOptions(): DataSourceOptions {
return {
type: process.env.DATABASE_TYPE,
host: process.env.DATABASE_HOST,
port: Number(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
} as DataSourceOptions;
}
import { DataSource, DataSourceOptions } from 'typeorm';
import dotenv from 'dotenv';
import getOptions from './getOptions.js';
dotenv.config();
let options = getOptions();
export default new DataSource({
...options,
entities: ['src/models/entities/*.ts'],
migrations: ['src/models/migrations/*.ts'],
} as DataSourceOptions);
import { DataSource, DataSourceOptions } from 'typeorm';
import dotenv from 'dotenv';
import getOptions from './getOptions';
import { User } from '../entities/User';
dotenv.config();
let options = getOptions();
export default async function executionDataSource() {
const datasource = new DataSource({
...options,
entities: [User],
});
if (!datasource.isInitialized) {
await datasource.initialize();
}
return datasource;
}
DataSource から DB にアクセスしていきますが、マイグレーション用と実行用とで分けています。
Next.js で TypeORM を使ううえで一番大事な箇所です。ここさえ分かればあとは問題ないと言えます。
問題になるのがentities: ['src/models/entities/*.ts'],
としているところで、この書き方は実行時にファイルを探しに行くかたちになります。マイグレーション時はts-node
を使用して実行するので問題ないのですが、Next.js 側から動かす場合は一度 JavaScript に変換し、そこから.ts
のファイルを探しに行こうとしてしまうのでエラーになってしまいます。そのため実行用ではトランスパイル時にファイルが特定できるようにentities: [User],
のような書き方にしています。
Entity が増えるたびにメンテナンスを行う必要があるので少し面倒ですが、entities/index.ts
を自動で用意できるのであればimport * as Entities from '../entities'
からentities: Object.values(Entities),
とすることでメンテナンスを不要にすることはできます。
今回は対象外とします。
また、プログラムから利用する場合は初期化が必要になるので、実行用は関数にして初期化処理を入れています。
Entity を作成した以下のコマンドを実行します。
npm run migration:generate --name=InitialSchema
src/models/migrations
配下に1731035020271-InitialSchema.ts
のようなファイルが生成されます。
中身はsrc/models/entities/User.ts
の内容に対応した CREATE 文になっているはずです。
続いて以下のコマンドを実行します。
npm run migration:run
DB にテーブルが作成されていることを確認してください。
DB 設定の最後にシーダーを作成します。
シーダーはデフォルトでは提供されていません。
単純にデータを登録するだけの処理を書きます。
import { exit } from 'process';
import datasource from '../datasources/migrationDataSource';
import { User } from '../entities/User';
import { EntityManager } from 'typeorm';
const data = [
{ name: '名前1', address: '住所1' },
{ name: '名前2', address: '住所2' },
{ name: '名前3', address: '住所3' },
{ name: '名前4', address: '住所4' },
];
(async () => {
if (!datasource.isInitialized) {
await datasource.initialize();
}
await datasource.transaction(async (entityManager: EntityManager) => {
const repo = entityManager.getRepository(User);
await repo.createQueryBuilder().insert().into(User).values(data).execute();
});
exit(0);
})();
以下のコマンドを実行してください。
npm run seeder --src=src/models/seeders/01_users_seeder.ts
テーブルにデータが追加されていることを確認してください。
画面への表示
ページのファイルを作成します。
import datasource from '@/models/datasources/executionDataSource';
import { User } from '@/models/entities/User';
export default async function Test1() {
const rep = (await datasource()).getRepository(User);
const data = await rep.createQueryBuilder().getMany();
return (
<>
<h1>Test1</h1>
<table border={1}>
<thead>
<tr>
<th>名前</th>
<th>住所</th>
</tr>
</thead>
<tbody>
{data.map((datum, i) => (
<tr key={i}>
<td>{datum.name}</td>
<td>{datum.address}</td>
</tr>
))}
</tbody>
</table>
</>
);
}
最後に以下コマンドでサーバを起動し、http://localhost:3000/test1
を開いてください。
npm run dev
シーダーで登録したデータが表示されていれば完成です。
一緒に働く仲間を募集中です!
リンクラフト株式会社では、組織拡大に伴い積極的な採用活動を行っています。
少しでも興味がある方はぜひご連絡ください。
▽会社ホームページ
https://lincraft.co.jp/
▽Instagram
https://www.instagram.com/lincraft.inc/
▽ご応募はこちらより
https://lincraft.co.jp/recruit
※カジュアル面談も受付中です。ご希望の方はHPのお問い合わせフォームよりご連絡ください。