Drizzle ORM×NuxtでMySQLと連携するWeb APIを作成してWijmoでCRUD処理を行う

「Drizzle ORM」はTypeScript向けの軽量・型安全なORM(Object-Relational Mapping)ツールです。Drizzle ORMを使用すると、SQLを直接書くことなくデータベースを操作できるので、開発者はデータベースとのやり取りをより直感的かつ安全に行うことができます。

本記事ではDrizzle ORMとNuxtを使用してMySQLと連携するWeb APIを作成し、JavaScript UIライブラリ「Wijmo(ウィジモ)」のデータグリッドコントロール「FlexGrid(フレックスグリッド)」と連携してデータの生成(Create)、読込(Read)、更新(Update)、削除(Delete)を行うCRUDアプリケーションを作成する方法をご紹介します。

開発環境

  • Nuxt 4.4.2
  • Vue 3.5.30
  • Drizzle ORM 0.45.2
  • Wijmo 5.20252.44

Nuxtプロジェクトの作成

まずはターミナルなどで以下のコマンドを実行して、ベースとなるNuxtのアプリケーションを作成します。

npx nuxi@latest init wijmo-drizzle-nuxt-app

いくつかインストールに関するオプションを選択していきます。テンプレートは「minimal」を選択します。

テンプレートの選択

パッケージマネージャーは「npm」を選択します。

パッケージマネージャーの選択

Nuxtの開発で使用する主なモジュールを選択して一緒にインストールすることができます。今回はCSSフレームワークとして「Tailwind CSS」を使用するのでので「Yes」を選択します。

モジュールのインストールの要否

「@nuxtjs/tailwindcss」を検索し、Spaceキー、またはTabキーを押下してモジュールを選択し、Enterキーを押下してインストールします。

Tailwind CSSのインストール

プロジェクトが作成されたら、以下のコマンドでプロジェクトの配下に移動し、アプリケーションを実行します。

cd wijmo-drizzle-nuxt-app
npm run dev

ブラウザで「http://localhost:3000/」にアクセスするとNuxtのアプリケーションが開きます。

Nuxtアプリケーションの実行

バックエンド部分の作成

Drizzle ORMのインストールと初期化

次に以下のコマンドを実行して、Drizzle ORMとMySQL2、ならびに環境変数を使用するためのdotenvの初期化を行います。

npm install drizzle-orm mysql2 dotenv

次にDrizzle ORMで使えるマイグレーションツールのDrizzle Kitをインストールします。

npm install -D drizzle-kit

MySQLとの接続設定とスキーマの定義

プロジェクトのルート直下に「 .env」ファイルを作成し、MySQLへの接続設定を記載します。
※ user、password、db_nameはお使いの環境に合わせて修正してください。

DATABASE_URL="mysql://user:password@localhost:3306/db_name"

次に「nuxt.config.ts」の中で先ほど定義したDB設定を保持します。

export default defineNuxtConfig({
  compatibilityDate: '2025-07-15',
  devtools: { enabled: true },
  modules: ['@nuxtjs/tailwindcss'],
  runtimeConfig: {
    databaseUrl: process.env.DATABASE_URL,
  },
})

続けて、プロジェクトのルート直下に「server/utils」フォルダを作成し、配下に「db.ts」ファイルを以下のように追加し、DB接続のユーティリティを作成します。

import mysql from 'mysql2/promise'
import { drizzle } from 'drizzle-orm/mysql2'

export function useDb() {
  const config = useRuntimeConfig()
  const pool = mysql.createPool({
    uri: config.databaseUrl!, // runtimeConfigから取得(サーバーのみ)
    connectionLimit: 10,
  })

  return drizzle({ client: pool })
}

次に「server」フォルダ配下に「db」フォルダを作成し、配下に「schema.ts」ファイルを以下のように追加し、OrderDataテーブルのスキーマを定義します。

import { int, mysqlTable, serial, varchar, datetime } from 'drizzle-orm/mysql-core'

export const orderData = mysqlTable('order_data', {
  id: serial('id').primaryKey(),
  product: varchar('product', { length: 255 }).notNull(),
  price: int('price').notNull(),
  quantity: int('quantity').notNull(),
  orderdate: datetime('orderdate').notNull(),
})

export type OrderRow = typeof orderData.$inferSelect
export type NewOrderRow = typeof orderData.$inferInsert

次にプロジェクトのルート直下に「drizzle.config.ts」ファイルを作成し、DBの接続設定を行います。

import 'dotenv/config'
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  out: './drizzle',
  schema: './server/db/schema.ts',
  dialect: 'mysql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
})

モデル定義を追記したら、以下のコマンドでMySQLにテーブルを作成します。

npx drizzle-kit push

実行が完了すると、「order_data」テーブルがMySQLに作成されます。

DrizzleORMでマイグレーション

APIルートの作成

次にAPIルートを作成していきます。ここではorderdataエンドポイント名にして、以下のように作成します。

  • GET:/api/orderdata(一覧参照)
  • POST:/api/orderdata(登録)
  • GET:/api/orderdata/:id(1件参照)
  • PUT:/api/orderdata/:id(更新)
  • DELETE:/api/orderdata/:id(削除)

「server」フォルダ配下に「api/orderdata」フォルダを作成し、配下に「index.get.ts」を追加してGET(一覧参照)の処理を記載します。

import { orderData } from '../../db/schema'
import { useDb } from '../../utils/db'

export default defineEventHandler(async () => {
  const db = useDb()
  return await db.select().from(orderData)
})

同様に「index.post.ts」を追加してPOST(登録)の処理を記載します。

import { orderData } from '../../db/schema'
import { useDb } from '../../utils/db'

type CreatePayload = {
  product: string
  price: number
  quantity: number
  orderdate: string
}

export default defineEventHandler(async (event) => {
  const body = await readBody<CreatePayload>(event)
  const db = useDb()

  const inserted = await db
    .insert(orderData)
    .values({
      product: body.product,
      price: body.price,
      quantity: body.quantity,
      orderdate: new Date(body.orderdate),
    })

  return { ok: true, inserted }
})

さらに「[id].get.ts」「[id].put.ts」「[id].delete.ts」をそれぞれ追加し、GET(1件参照)、PUT(更新)、DELETE(削除)の処理を定義します。

import { eq } from 'drizzle-orm'
import { orderData } from '../../db/schema'
import { useDb } from '../../utils/db'

export default defineEventHandler(async (event) => {
  const id = Number(getRouterParam(event, 'id'))
  const db = useDb()

  const rows = await db.select().from(orderData).where(eq(orderData.id, id))
  if (!rows[0]) {
    throw createError({ statusCode: 404, statusMessage: 'Not Found' })
  }
  return rows[0]
})
import { eq } from 'drizzle-orm'
import { orderData } from '../../db/schema'
import { useDb } from '../../utils/db'

type UpdatePayload = {
  product: string
  price: number
  quantity: number
  orderdate: string
}

export default defineEventHandler(async (event) => {
  const id = Number(getRouterParam(event, 'id'))
  const body = await readBody<UpdatePayload>(event)
  const db = useDb()

  await db
    .update(orderData)
    .set({
      product: body.product,
      price: body.price,
      quantity: body.quantity,
      orderdate: new Date(body.orderdate),
    })
    .where(eq(orderData.id, id))

  return { ok: true }
})
import { eq } from 'drizzle-orm'
import { orderData } from '../../db/schema'
import { useDb } from '../../utils/db'

export default defineEventHandler(async (event) => {
  const id = Number(getRouterParam(event, 'id'))
  const db = useDb()

  await db.delete(orderData).where(eq(orderData.id, id))
  return { ok: true }
})

作成したら以下のコマンドを実行してAPIを起動します。

npm run dev

curlなどで「http://localhost:3000/api/orderdata」にGETリクエストを送信すると、APIからデータが取得できます(この時点では0件)。

GETリクエスト

Drizzle Studioでデータの編集

Drizzle ORMはGUIでDBのデータ管理が可能な「Drizzle Studio」を提供しています。

以下のコマンドを実行することでDrizzle Studioを起動できます。

npx drizzle-kit studio

ブラウザから「https://local.drizzle.studio/」にアクセスすると以下のような管理画面でGUIによるデータ編集が可能です。

Drizzle Studio

こちらから何件かテストデータを登録しておきます。

フロントエンド部分の作成

以上でバックエンドのWeb APIの準備が整ったので、WijmoのFlexGridでデータ表示を行うフロントエンド部分を作成していきます。

Wijmoのインストールと組み込み

まずはWijmoのVue用パッケージをアプリケーションにインストールします。

npm install @mescius/wijmo.vue2.all

npmパッケージをインストールしたら、Wijmoの組み込みを行なっていきます。

まずは「app」フォルダ配下に「components」フォルダを作成します。

componentsフォルダ

「components」フォルダに「FlexGrid.vue」を追加し、以下のように記述します。

WijmoのVueモジュールやCSS、日本語カルチャファイルをインポートし、Fetch APIを使用してWeb APIからデータを取得します。ライセンスキーは「 .env」ファイルに保持します。
※ ライセンスキーを設定しない場合トライアル版を示すメッセージが表示されます。ライセンスキーの入手や設定方法についてはこちらをご覧ください。

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import '@mescius/wijmo.styles/wijmo.css'
import '@mescius/wijmo.cultures/wijmo.culture.ja'

import * as wjCore from '@mescius/wijmo'
import { WjFlexGrid, WjFlexGridColumn } from '@mescius/wijmo.vue2.grid'
import { WjFlexGridFilter } from '@mescius/wijmo.vue2.grid.filter'

type OrderItem = {
  id: number
  product: string
  price: number
  quantity: number
  orderdate: Date
}

const config = useRuntimeConfig()

// Wijmoライセンスキーの設定
wjCore.setLicenseKey(config.public.wijmoLicenseKey || '');

const cv = ref<wjCore.CollectionView<OrderItem> | null>(null)
const loading = ref(true)

async function load() {
  loading.value = true
  const response = await fetch('/api/orderdata')
  const text = await response.text()
  const rows = JSON.parse(text, reviver) as OrderItem[]
  cv.value = new wjCore.CollectionView(rows, { trackChanges: true, pageSize: 15 })
  loading.value = false
}

onMounted(load)

async function saveChanges() {
  if (!cv.value) return

  const view = cv.value
  const url = '/api/orderdata'

  // orderdateをISO 文字列へ正規化する
  const normalizeOrderdate = (v: any): string => {
    if (v instanceof Date) return v.toISOString()

    if (typeof v === 'string') {
      const s = v.trim()
      const normalized = s.replace(/\//g, '-')
      const d = new Date(normalized)
      if (!isNaN(d.getTime())) return d.toISOString()
    }

    const d = new Date(v)
    if (!isNaN(d.getTime())) return d.toISOString()

    throw new Error('受注日が未入力、または日付形式が不正です。')
  }

  // 追加
  await Promise.all(
    view.itemsAdded.map((x: any) =>
      fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...x, orderdate: normalizeOrderdate(x.orderdate) }),
      })
    )
  )

  // 更新
  await Promise.all(
    view.itemsEdited.map((x: any) =>
      fetch(`${url}/${x.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...x, orderdate: normalizeOrderdate(x.orderdate) }),
      })
    )
  )

  // 削除
  await Promise.all(
    view.itemsRemoved.map((x: any) =>
      fetch(`${url}/${x.id}`, { method: 'DELETE' })
    )
  )

  await load()
  alert('変更を保存しました')
}

// 日付文字列をDate型に変換するreviver関数
function reviver(key: string, val: any) {
    if (
        typeof val === "string" &&
        /^\d{4}-\d{2}-\d{2}T.*/.test(val)
    ) {
        return new Date(Date.parse(val));
    }
    return val;
}
</script>

<template>
  <div class="space-y-3">
    <div class="flex gap-2 items-center">
      <button class="px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
              :disabled="loading || !cv"
              @click="saveChanges">
        更新
      </button>
      <span v-if="loading">Loading...</span>
    </div>

    <wj-flex-grid
      v-if="cv"
      :itemsSource="cv"
      :allowAddNew="true"
      :allowDelete="true"
      style="width: 785px"
    >
      <wj-flex-grid-filter />
      <wj-flex-grid-column header="ID" binding="id" :isReadOnly="true" :width="80" />
      <wj-flex-grid-column header="製品名" binding="product" :width="240" />
      <wj-flex-grid-column header="単価" binding="price" :width="120" />
      <wj-flex-grid-column header="数量" binding="quantity" :width="120" />
      <wj-flex-grid-column header="受注日" binding="orderdate" :width="180" />
    </wj-flex-grid>
  </div>
</template>
DATABASE_URL="mysql://user:password@localhost:3306/db_name"
NUXT_PUBLIC_WIJMO_LICENSE_KEY="ここにWijmoのライセンスキーを設定します"

「nuxt.config.ts」のruntimeConfigに環境変数の設定を追加します。また、ssr: falseオプションを指定して、SSR(サーバーサイドレンダリング)しないように設定します。さらに、ページのタイトルや言語の設定なども追加します。

export default defineNuxtConfig({
  compatibilityDate: '2025-07-15',
  devtools: { enabled: true },
  modules: ['@nuxtjs/tailwindcss'],
  ssr: false, 
  runtimeConfig: {
    databaseUrl: process.env.DATABASE_URL,
    public: {
      wijmoLicenseKey: process.env.NUXT_PUBLIC_WIJMO_LICENSE_KEY,
    },
  },
  app: {
    head: {
      title: 'Wijmo×Drizzle CRUDサンプル',
      meta: [
        {
          name: 'description',
          content: 'NuxtとDrizzleを使用したWijmoのCRUDアプリケーションサンプルです。',
        },
      ],
      htmlAttrs: {
        lang: 'ja',
      },
    },
  },
})

仕上げとして「app/app.vue」ファイルの内容を以下のように書き換えて、作成したFlexGridコンポーネントを組み込みます。

<template>
  <div style="margin-left: 10px">
    <h1 class="text-3xl font-bold text-sky-600 my-3">Wijmo×Drizzle CRUDサンプル</h1>
    <FlexGrid />
  </div>
</template>

データの読込(READ)

以上の手順で、Wijmoの組み込みは完了です。再び「npm run dev」コマンドを実行して「http://localhost:3000/」にブラウザでアクセスすると、FlexGrid上にAPIから取得したデータが表示されていることを確認できます。

データの読込(READ)

今回のサンプルでは、FlexGridFilterコンポーネントも組み込んでいるため、Excelライクなソートやフィルタも利用可能です。

・・・(中略)・・・
import { WjFlexGridFilter } from '@mescius/wijmo.vue2.grid.filter'
・・・(中略)・・・
    <wj-flex-grid
      v-if="cv"
      :itemsSource="cv"
      :allowAddNew="true"
      :allowDelete="true"
      style="width: 785px"
    >
      <wj-flex-grid-filter />
      <wj-flex-grid-column header="ID" binding="id" :isReadOnly="true" :width="80" />
      <wj-flex-grid-column header="製品名" binding="product" :width="240" />
      <wj-flex-grid-column header="単価" binding="price" :width="120" />
      <wj-flex-grid-column header="数量" binding="quantity" :width="120" />
      <wj-flex-grid-column header="受注日" binding="orderdate" :width="180" />
    </wj-flex-grid>
  </div>
</template>

データの登録(CREATE)

FlexGridの一番下の行にデータを入力することで新規データの登録が可能です。データ入力後[更新]ボタンを押下するとAPIにPOSTのリクエストを送信できます。

データの更新(UPDATE)

FlexGrid上で任意のデータを更新し、[更新]ボタンを押下するとAPIにPUTのリクエストを送信できます。一度に複数件の更新も可能です。

データの削除(DELETE)

FlexGrid上で削除したい行を選択しDeleteキーを押下すると対象の行を削除できます。その後、[更新]ボタンを押下するとAPIにDELETEのリクエストを送信できます。

今回作成したアプリケーションは以下よりダウンロード可能です。

さいごに

今回はDrizzle ORMとNuxtを使用してを使用してMySQLと連携するWeb APIを作成し、Wijmoのデータグリッド「FlexGrid」でCRUD処理を行うWebアプリケーションを作成する方法をご紹介しました。

DrizzleはTypeScriptとの親和性が高く、SQLライクな記述でデータベース操作を行えるため、Nuxtのようなモダンなフレームワークと組み合わせることで、シンプルかつ保守性の高い実装が可能になります。また、WijmoのFlexGridを利用することで、一覧表示・編集・追加・削除といった業務アプリケーションで頻出するUIも効率よく実装できます。

製品サイトでは、Wijmoの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /