Supabase上のPostgreSQLをSequelizeでDBマイグレーションしてみよう

  • Supabase
  • DB
  • 効率化

今回は、SequelizeというORMを使って、Supabase上のPostgreSQLを管理できるようにしてみようという記事です。

Supabaseについての説明は割愛します。検索すると熱く紹介している記事がちらほらあるので、そちらをご覧ください。

SupabaseのプロジェクトではPostgreSQLが利用でき、管理画面からクエリの実行等ができます。管理画面のUIはわかりやすく、かなり好感が持てます。さらに、Supabase CLIを利用することでDockerコンテナでローカルに環境を構築することも可能です。開発時はローカルで立てた環境を使うといった選択肢もあります。複数人で開発するような時は良さそうですね。Supabase CLIは現在鋭意開発中のようで、今後機能が増えていくようです。DBマイグレーションはコマンドからできるのかなと思ったのですが、リポジトリのREADMEを見る限り、現時点(2021年11月25日)では開発中のようでした。(今後機能追加されると思うので、気になる方はリポジトリをチェックしてください)

そんなわけでSequelizeでSupabaseで作成したプロジェクト上のPostgreSQLに接続して、DBマイグレーションできるようにしてみましょう。

まずは準備

コマンドの準備とSequelize CLIの初期化

DBマイグレーションの準備をしていきます。

まずは、必要なパッケージをインストールします。

yarn add -D pg sequelize sequelize-cli

続いて、プロジェクトのルートに.sequelizercという設定ファイルを用意します。後述するsequelize-cliの初期化コマンドを実行するとconfigmodelsseedersmigrationsディレクトリの4つが作成されるのですが、これらを格納しておくディレクトリを指定するために設定ファイルを用意します。内容は次のようにします。

const path = require('path')
// sequelizeというディレクトリに格納する
const ROOT_DIR = path.resolve(__dirname, 'sequelize')
module.exports = {
  config: path.join(ROOT_DIR, 'config', 'config.cjs'),
  'models-path': path.join(ROOT_DIR, 'models'),
  'seeders-path': path.join(ROOT_DIR, 'seeders'),
  'migrations-path': path.join(ROOT_DIR, 'migrations'),
}

そして、初期化コマンドを実行します。これにより、必要なディレクトリ、ファイルが生成されます。

yarn sequelize-cli init

DB接続

コマンドの用意はできたので、今度はSupabaseのプロジェクト上のPostgreSQLに接続してみます。

設定方法の詳細はSequelize CLIのリポジトリにあるREADMEをご覧ください。

先ほど自動生成されたconfigディレクトリ中にconfig.jsonがあるかと思いますが、そこに接続先のDBの情報を記入します。自動生成されるものはJSON形式ですが、JSで置き換えてカスタマイズしてもOKです。

なお、接続するDBの情報はコマンド実行時に環境変数に指定して渡すこともできますし、前述したconfigをカスタマイズしてdotenv.envファイル)から読み込むようにもできます。

今回は.envファイルから読むようにカスタマイズしてみましょう。また、developmentとproductionなどを指定して読み込む.envファイルを分けれるようにしてみます。

まずは、dotenvをインストールします。

yarn add -D dotenv

.env.developmentファイルを作成します。

# 区別がつきやすいように変数名のプレフィックスにSEQUELIZEをつけておきます
# ※各変数の内容は入れてください
SEQUELIZE_SUPABASE_DB_HOST={内容を入れてね}
SEQUELIZE_SUPABASE_DB_PORT=5432
SEQUELIZE_SUPABASE_DB_DATABASE={内容を入れてね}
SEQUELIZE_SUPABASE_DB_USERNAME={内容を入れてね}
SEQUELIZE_SUPABASE_DB_PASSWORD={内容を入れてね}

続いて、configディレクトリ(sequelize/config/)にdotenvで読み込むためのJSファイルを用意します。ファイル名はload-variables.jsとしておきます。

const fs = require('fs')
const path = require('path')
const dotenv = require('dotenv')

// コマンド実行時にNODE_ENVを指定することで読み込むdotenvファイルを変えれるようにする
// 未指定の場合はdevelopmentとする
const nodeEnv = process.env.NODE_ENV || 'development'
const envFilePath = path.resolve(__dirname, '../..', `.env.${nodeEnv}`)
console.info(`Loaded dotenv file "${envFilePath}".`)

const parseOutput = dotenv.parse(fs.readFileSync(envFilePath))

const envConfig = {}

for (const key in parseOutput) {
  // SEQUELIZE_ から始まる変数のみを扱うようにする
  if (/^SEQUELIZE_.+$/.test(key)) {
    envConfig[key] = parseOutput[key]
  }
}

module.exports = envConfig

そして、config.js(sequelize/config/config.js)を次のようにします。

const envConfig = require('./load-variables')

module.exports = {
  dialect: 'postgres',
  host: envConfig.SEQUELIZE_SUPABASE_DB_HOST,
  port: envConfig.SEQUELIZE_SUPABASE_DB_PORT,
  database: envConfig.SEQUELIZE_SUPABASE_DB_DATABASE,
  username: envConfig.SEQUELIZE_SUPABASE_DB_USERNAME,
  password: envConfig.SEQUELIZE_SUPABASE_DB_PASSWORD,
}

最後にindex.jsファイル(sequelize/models/index.js)を変更します。ほとんど自動生成されたもののままですが一部だけ変えています。

const fs = require('fs')
const path = require('path')
const Sequelize = require('sequelize')
const basename = path.basename(__filename)
const config = require('../config/config')
const db = {}

let sequelize
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config)
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config)
}

fs.readdirSync(__dirname)
  .filter((file) => {
    return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
  })
  .forEach((file) => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes)
    db[model.name] = model
  })

Object.keys(db).forEach((modelName) => {
  if (db[modelName].associate) {
    db[modelName].associate(db)
  }
})

db.sequelize = sequelize
db.Sequelize = Sequelize

module.exports = db

dotenvで読み込む例を書きましたが、そこまで重要なことではないので自動生成されたconfig.jsonを使う方法でも全然良いです。(半分は自分のメモ用と思って書いています)

長くなりましたが、DBマイグレーションをするための準備ができました。

DBマイグレーションしてみる

あとはSequelize CLIを使って、マイグレーションファイルを作って、テーブルの作成等をする流れになります。適当にマイグレーションファイルでテーブル定義を書いて、接続テストを兼ねて yarn sequelize-cli db:migrate を実行してみましょう。

# id, name, createdAt, updatedAtのカラムをもつaccountテーブルの定義ファイルが作成されます。
yarn sequelize-cli model:generate --name account --attributes name:string

# マイグレーション
yarn sequelize-cli db:migrate

次に、RLS(Row Level Security)を有効にしてみましょう。

# 実行するとsequelize/migrations/[実行日時]-create-policy.jsというファイルができます
yarn sequelize-cli migration:generate --name create-policy

上記コマンドで生成されたJSファイルを編集します。

あくまで例ですが、次のような感じでクエリを実行してRLSの有効化とポリシーの定義ができます。

module.exports = {
  up: async (queryInterface) => {
    await queryInterface.sequelize.transaction(async (transaction) => {
      // accountテーブルでRLSを有効化
      await queryInterface.sequelize.query(`ALTER TABLE account ENABLE ROW LEVEL SECURITY`, { transaction })

      // ポリシー: SELECT
      await queryInterface.sequelize.query(
        `
          CREATE POLICY "Users can view their own account." ON account
          FOR SELECT
          USING (
            id = auth.uid()
          )
        `,
        { transaction }
      )
      // ポリシー: INSERT
      await queryInterface.sequelize.query(
        `
          CREATE POLICY "Users can insert their own account." ON account
          FOR INSERT
          WITH CHECK (
            id = auth.uid()
          )
        `,
        { transaction }
      )
  },
  down: async (queryInterface) => {
    await queryInterface.sequelize.transaction(async (transaction) => {
      await queryInterface.sequelize.query(`DROP POLICY IF EXISTS "Users can view their own account." ON account`, { transaction })
      await queryInterface.sequelize.query(`DROP POLICY IF EXISTS "Users can insert their own account." ON account`, { transaction })
      await queryInterface.sequelize.query(`ALTER TABLE account DISABLE ROW LEVEL SECURITY`, { transaction })
    })
  },
}

編集後にyarn sequelize-cli db:migrateでマイグレーションします。

Supabaseのプロジェクト管理画面でRLSが有効になっていること設定したポリシーが反映されていることが確認できるかと思います。

おわりに

今回はSequelizeを使ってSupabaseのPostgreSQLをDBマイグレーションしてみようというテーマで紹介しましたが、別にSupabase上のPostgreSQLに限った話ではありません。接続先のDBを変えれば、いろんなところで応用して使えるかと思います。

個人でSupabaseを使っていてDBのマイグレーションはどうしようかなと思っていたので、あえて「SupabaseのPostgreSQL」を対象としたテーマで取りあげてみました。

Sequelizeをまだ使って日が浅いので、少しずつ使いこなせていければなと思います。

以上です。

検索で、偶然この記事に辿り着いた人のご参考になれば嬉しいです。

この記事を共有

アバター

K.Utsunomiya
主にWeb技術について投稿していきます。
詳しいプロフィール