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というパッケージ名に変更されています。
1 2 3 4 5 6 |
$ 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上でのエラーも消えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 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
1 2 3 4 5 6 7 |
{ "extends": "../.nuxt/tsconfig.json", "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } |
モデル定義
sequelize-cliを使って環境を初期化します。databaseディレクトリ の下に関連ファイルを置くこととします。models/index.jsは使わないので削除しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ ./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
1 2 3 4 5 6 |
{ "development": { "dialect": "sqlite", "storage": "./db_dev.sqlite" } } |
続いてモデルを作成していきます。今回は、2つのテーブルからなるシンプルな構成とします。
database/models/index.ts
ER図は以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
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
1 2 3 4 5 6 |
import { Post } from "~/database/models"; export default defineEventHandler(async (event) => { const posts = await Post.findAll(); return posts; }); |
server/api/posts.post.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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; }); |
実行
準備ができましたので、アプリケーションを起動してみます。
1 |
$ 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)
コメント