ORMであるsequelizeのバージョン7(執筆時点でalpha)では、デコレータを使ったモデル定義が可能です。本記事では、Nuxt3でsequelize7を使ってデータベースを検索してその結果を返すまでのステップを記述します。
テスト環境
- mac 10.15.7
- Node.js v20.13.0
- nuxt@3.11.2
- sequelize@6.37.3
- @sequelize/core@7.0.0-alpha.41
セットアップ
Nuxt3とsequelizeをインストールします。今回データベースはSQLite3を使います。sequelizeライブラリは、sequelize-cliによるデータベース作成のためにインストールします。sequelize-cliはsequelize7にまだ対応していないためです。なお、sequelize7では、sequelizeは@sequelize/coreというパッケージ名に変更されています。
$ pnpm dlx nuxi@latest init nuxt-sequelize7 $ cd nuxt-sequelize7 # corepackの制限を一時的にoffにしたい場合 $ export COREPACK_ENABLE_STRICT=0 $ pnpm add @sequelize/core@alpha @sequelize/sqlite3 sequelize zod bcrypt $ pnpm add -D sequelize-cli @types/brcypt
nuxt.config.ts
デコレータを使えるようにするため、以下の設定が必要です。TypeScriptのコンパイルオプションで、experimentalDecoratorsをtrueにします。また、tsconfig.jsonにも設定を加えます。VSCode上でのエラーも消えます。
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
nitro: {
esbuild: {
options: {
tsconfigRaw: {
// See https://github.com/nuxt/nuxt/issues/14126
// https://github.com/unjs/nitro/issues/273
compilerOptions: {
experimentalDecorators: true,
},
},
},
},
},
});
tsconfig.json
{
"extends": "../.nuxt/tsconfig.json",
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
モデル定義
sequelize-cliを使って環境を初期化します。databaseディレクトリ の下に関連ファイルを置くこととします。models/index.jsは使わないので削除しておきます。
$ ./node_modules/.bin/sequelize-cli init --migrations-path database/migrations --models-path database/models --seeders-path database/seeders $ tree config database config └── config.json database ├── migrations ├── models │ └── index.js └── seeders 3 directories, 2 files $ rm database/models/index.js
今回はSQLiteを使うのでconfig.jsonを修正します。
config/config.json
{
"development": {
"dialect": "sqlite",
"storage": "./db_dev.sqlite"
}
}
続いてモデルを作成していきます。今回は、2つのテーブルからなるシンプルな構成とします。
database/models/index.ts
ER図は以下のとおりです。

import {
Sequelize,
type HasManyCreateAssociationMixin,
type InferAttributes,
type InferCreationAttributes,
type Options,
} from "@sequelize/core";
import config from "../../config/config.json";
import {
DataTypes,
Model,
type CreationOptional,
type NonAttribute,
} from "@sequelize/core";
import {
Attribute,
HasMany,
NotNull,
Table,
CreatedAt,
UpdatedAt,
PrimaryKey,
AutoIncrement,
BeforeCreate,
BeforeUpsert,
} from "@sequelize/core/decorators-legacy";
import type { SqliteDialect } from "@sequelize/sqlite3";
import bcrypt from "bcrypt";
const environment = process.env.NODE_ENV || "development";
const options = config[environment as keyof typeof config];
export const sequelize = new Sequelize({
...options,
} as Options<SqliteDialect>);
@Table({
tableName: "Users",
indexes: [{ fields: ["email"], unique: true }],
})
export class User extends Model<
InferAttributes<User>,
InferCreationAttributes<User>
> {
@Attribute(DataTypes.INTEGER.UNSIGNED)
@PrimaryKey
@AutoIncrement
declare readonly id: CreationOptional<number>;
@Attribute(DataTypes.STRING)
@NotNull
declare name: string;
@Attribute(DataTypes.STRING)
@NotNull
declare email: string;
@Attribute(DataTypes.STRING)
@NotNull
declare password: string;
@CreatedAt
declare readonly createdAt: CreationOptional<Date>;
@UpdatedAt
declare readonly updatedAt: CreationOptional<Date>;
@HasMany(() => Post, {
foreignKey: "userId",
inverse: {
as: "author",
},
})
declare posts?: NonAttribute<Post[]>;
declare createPost: HasManyCreateAssociationMixin<Post, "userId">;
@BeforeCreate
static async hashPassword(instance: User) {
const password = instance.getDataValue("password");
if (password) {
const hashedPassword = bcrypt.hashSync(password, 10);
instance.setDataValue("password", hashedPassword);
}
}
}
@Table({
tableName: "Posts",
})
export class Post extends Model<
InferAttributes<Post>,
InferCreationAttributes<Post>
> {
@Attribute(DataTypes.INTEGER.UNSIGNED)
@PrimaryKey
@AutoIncrement
declare readonly id: CreationOptional<number>;
@Attribute(DataTypes.STRING)
@NotNull
declare title: string;
@Attribute(DataTypes.STRING)
@NotNull
declare content: string;
@Attribute({
type: DataTypes.INTEGER.UNSIGNED,
references: { table: "Users", key: "id" },
onUpdate: "CASCADE",
onDelete: "CASCADE",
})
declare userId: number;
@CreatedAt
declare readonly createdAt: CreationOptional<Date>;
@UpdatedAt
declare readonly updatedAt: CreationOptional<Date>;
/** Declared by {@link User#posts} */
declare author?: NonAttribute<User>;
}
sequelize.addModels([Post, User]);
await sequelize.sync();
// 簡単のためここでテストデータを作成
const user = await User.findOne({
where: { email: "admin@example.com" },
});
if (!user) {
await User.create({
name: "admin",
email: "admin@example.com",
password: "password",
});
}
APIの作成
記事投稿と参照のAPIを定義します。
server/api/posts.get.ts
import { Post } from "~/database/models";
export default defineEventHandler(async (event) => {
const posts = await Post.findAll();
return posts;
});
server/api/posts.post.ts
import { z } from "zod";
import { User } from "~/database/models";
export default defineEventHandler(async (event) => {
const body = await readValidatedBody(
event,
z.object({
title: z.string(),
content: z.string(),
}).parse,
);
const user = await User.findOne({ where: { name: "admin" } });
const post = await user!.createPost(body);
return post;
});
実行
準備ができましたので、アプリケーションを起動してみます。
$ pnpm run dev
VSCodeでデータベースを覗いてみましょう。VSCodeでSQLiteエクステンションをインストールすると、 VSCode内でデータベースを見ることができます。ちゃんとデータベースが作成され、ユーザーが作成されていることがわかります。

今回は画面を作成せず、devtoolsでAPIをコールしてみます。
まずは、記事を投稿してみます。良さそうですね。

続いて記事の参照。こちらも良さそうですね。

まとめ
Nuxt3でSequelize7を使用してみました。
Sequelize7ではモデル定義でデコレータを使用できます。なお、機能を使用するにはTypeScriptのexperimentalDecoratorsオプションを有効化する必要があります。また、nitroでも(内部でesbuildを使用)同様にオプションを有効化する必要があります。
参考リンク
- Sequelize v7 (alpha) | Sequelize
- Experimental typescript decorator support · Issue #273 · unjs/nitro (github.com)
サンプルプログラム
今回のサンプルプログラムは以下です。
moritetu/nuxt-sequelize7: nuxt3+sequelize7 sample (github.com)


コメント