🐧
Remult + SvelteKitが良さげ
- Web開発において汎用性かつ機能性の高い最適なAPIの開発やドキュメントの作成は非常に重要です。
- 今回は「型安全なAPIの自動生成・容易なリアルタイムデータ同期」が可能なRemultとFWのSveltekitを利用して素早くAPIやWebアプリを作成する方法を記録いたします。
環境
- macOS 13.6.8
- node 22.0.0
手順
Svelteプロジェクトの構築
- 任意のディレクトリで以下のコマンドで、Sveltekitプロジェクトを作成します。
# svelteインストール
$ npm create svelte@latest sample
Need to install the following packages:
create-svelte@6.4.0
Ok to proceed? (y) y
create-svelte version 6.4.0
┌ Welcome to SvelteKit!
│
◇ Which Svelte app template?
│ Skeleton project
│
◇ Add type checking with TypeScript?
│ Yes, using TypeScript syntax
│
◇ Select additional options (use arrow keys/space bar)
│ Add ESLint for code linting, Add Prettier for code formatting
│
└ Your project is ready!
# 移動
cd sample
# パッケージインストール
npm i
# 開発サーバー起動確認。
npm run dev
Remultインストール
- 次に以下のコマンドで、型安全なCRUD APIを構築するためにRemultをインストールします。
$ npm i remult --save-dev
- 次に
tsconfig.json
のcompilerOptions
に以下を追加します。- Remultではデコレーターベースでエンティティを書いていくため。
"experimentalDecorators": true
APIルートの作成
- 次に以下のコマンドで、APIルートを作成するためのディレクトリおよびファイルを作成します。
- Sveltekitではroutes配下に
+server
ファイルを作成することでAPIルートを定義できます。
- Sveltekitではroutes配下に
# apiディレクトリの作成
mkdir src/routes/api
# [...remult]ディレクトリの作成
mkdir "src/routes/api/[...remult]"
# サーバーファイルの作成
touch "src/routes/api/[...remult]/+server.ts"
- 作成した
+server.ts
を以下の内容に修正します。
import { remultSveltekit } from 'remult/remult-sveltekit'
// +serverファイルでexportできるのは「HTTP対応関数」「頭にアンダースコアがついたもの」のみ。
export const _api = remultSveltekit({})
// HTTPメソッド対応関数をエクスポートすることでAPIを作成できる。
export const { GET, POST, PUT, DELETE } = _api
エンティティの定義
- セットアップは完了したので、次にエンティティを定義していきます。
- Remultではデータのエンティティ(スキーマ)ベースであり、この定義をもとにAPIのクエリやドキュメント等の自動生成を行います。
- 今回は以下を定義して本の管理を行えるものにします。
- Book: 本の情報
- Category: 本に紐づくカテゴリ情報
- 以下のコマンドで、定義するためのディレクトリおよびファイルを作成します。
mkdir src/shared
touch src/shared/Book.ts
touch src/shared/Category.ts
- 作成後、まず
Category.ts
の中身を以下の内容に修正します。
import { Entity, Fields, Validators } from 'remult';
@Entity('categories', {
allowApiCrud: true
})
export class Category {
@Fields.autoIncrement()
id!: string;
@Fields.string({
validate: Validators.required
})
name: string = '';
@Fields.createdAt()
createdAt?: Date;
@Fields.updatedAt()
updatedAt?: Date;
}
-
上記のコードの簡単な説明は以下です。
-
次に
Book.ts
の中身を以下の内容に修正します。
import { Entity, Fields, Relations, Validators } from 'remult';
import { Category } from './Category';
@Entity('books', {
allowApiCrud: true
})
export class Book {
@Fields.autoIncrement()
id!: string;
@Fields.string({
validate: Validators.required
})
title: string = '';
@Relations.toOne(() => Category)
category?: Category;
@Fields.createdAt()
createdAt?: Date;
@Fields.updatedAt()
updatedAt?: Date;
}
- 上記のコードの簡単な説明は以下です。
- @Relations.toOne(() => Entity名): 多対一のリレーションの定義。
- Remultではこうしたデータベース間の関連性をシンプルなデコレータで簡単に定義できます。
- 他のリレーションはこちら
- @Relations.toOne(() => Entity名): 多対一のリレーションの定義。
エンティティの登録
- Remultではエンティティを登録するだけでCRUDやページング等を備えたAPIが自動生成されます。
- 上記で作成した
routes/api/[...remult]/+server.ts
を以下の内容に修正します。
import { remultSveltekit } from 'remult/remult-sveltekit';
import { Book } from '../../../shared/Book';
import { Category } from '../../../shared/Category';
export const _api = remultSveltekit({
// エンティティティの登録
entities: [Book, Category]
});
export const { GET, POST, PUT, DELETE } = _api;
- 登録後、以下のコマンドを実行してひとまずGET APIが実行可能なことを確認します。
npm run dev
# データを入れていないので、まだ空
$ curl -X GET http://localhost:5173/api/books
[]
Swaggerの用意
- 作成したAPIの「各種リクエストのテスト」「エンドポイント一覧やスキーマ・パラメータの確認」を簡単に行うべくSwaggerを利用します。
- Remultには作成したAPIのOpen API Documentを自動生成する機能があるので、その機能で作成されたものをSwaggerに設定します。
- まず以下のコマンドでswagger uiライブラリをインストールします。
npm i swagger-ui
npm i --save-dev @types/swagger-ui
- 次にOpen API Documentをフロントエンドに渡すために
routes/api/[...remult]/+server.ts
を以下の内容に修正します。
import { remultSveltekit } from 'remult/remult-sveltekit';
import { Book } from '../../../shared/Book';
import { Category } from '../../../shared/Category';
export const _api = remultSveltekit({
entities: [Book, Category]
});
// 追加。アンダースコアをつけてフロントからimportできるようにする。
export const _openApiDoc = _api.openApiDoc({
title: 'SAMPLE'
});
export const { GET, POST, PUT, DELETE } = _api;
- 次にswagger uiを表示するためのルーティングとして以下でディレクトリおよびファイルを作成します。
- 今回は
localhost:5173/docs
にアクセスした時にswagger uiにアクセスできるようにします。
- 今回は
# swagger uiパス
mkdir src/routes/docs
# indexページ
touch src/routes/docs/+page.svelte
# サーバーからのデータを取得してフロントで扱うためのファイル
touch src/routes/docs/+page.server.ts
- 次に
src/routes/docs/+page.server.ts
を以下の内容に修正します。
import { _openApiDoc } from '../api/[...remult]/+server';
export const load = async () => {
return {
// svelteファイルでdata.apiDocのように利用できるようになる。
apiDoc: _openApiDoc
};
};
- 次に
src/routes/docs/+page.svelte
を以下の内容に修正します。
<script lang="ts">
import { onMount } from 'svelte';
import SwaggerUI from 'swagger-ui';
import 'swagger-ui/dist/swagger-ui.css';
import type { PageData } from './$types';
// +page.server.tsからのデータ
export let data: PageData;
onMount(async () => {
SwaggerUI({
// remultで作成されたdocumentを設定
spec: data.apiDoc,
dom_id: '#swagger-ui-container'
});
});
</script>
<svelte:head>
<title>SwaggerUI</title>
</svelte:head>
<div id="swagger-ui-container" />
- 修正後、
npm run dev
してlocalhost:5173/docs
にアクセスしてswagger uiが表示されていることを確認します。
- 以下で自動生成されたAPIは単純なCRUDだけでなくソートやページング等も備えていて、また自動生成されたOpen API Documentはスキーマやexampleの定義も行なっていることを確認します。
- 以下でリクエストのテストも正常に行えることを確認します。
データ
- RemultではデフォルトのDBはローカルの
db/テーブル名.json
に格納される。 - DBはjsonだけでなく、もちろんMySQLやPostgresなどに対応していてこちらを参考に設定可能。
- つまり、開発環境ではプレーンなjsonファイルを利用して、本番環境はPostgresを利用するといったことが可能。
フロントでの表示
- ここまででエンティティ定義だけで簡単に最適なAPIを作成できました。
- 最後にそのAPIをフロントエンドで利用して画面に表示することまで行います。
-
src/routes/+page.svelte
ファイルを以下の内容に修正します。
<script lang="ts">
import { remult } from 'remult';
import { onMount } from 'svelte';
import { Book } from '../shared/Book';
import { Category } from '../shared/Category';
let books: Book[] = [];
let categories: Category[] = [];
let newBookTitle = '';
let selectedCategory: Category;
onMount(async () => {
[books, categories] = await Promise.all([
// 本取得。リレーションのカテゴリもレスポンスに含める。
remult.repo(Book).find({
include: {
category: true
}
}),
// カテゴリ全取得
remult.repo(Category).find()
]);
});
// 登録
const addBook = async () => {
const newBook = await remult.repo(Book).insert({
title: newBookTitle,
category: selectedCategory
});
books = [...books, newBook];
newBookTitle = '';
};
</script>
<main>
<form on:submit|preventDefault={addBook}>
<input id="title" bind:value={newBookTitle} required />
<select id="category" bind:value={selectedCategory}>
<option value="">カテゴリを選択</option>
{#each categories as category}
<option value={category}>{category.name}</option>
{/each}
</select>
<button type="submit">登録</button>
</form>
{#each books as book (book.id)}
<p>{book.title} {book.category?.name || 'カテゴリなし'}</p>
{/each}
</main>
-
上記のコードの簡単な説明は以下です。
- remult.repo(エンティティ名): エンティティのCRUD操作。
- リポジトリパターンを採用していて、repoを通してCRUD関連操作を行う。
- 一覧はこちら
- remult.repo(エンティティ名).find(): 取得。
- デフォルトでは100件取得。
- その他limitやwhereなど。
- repo.find({include}): データに含めるリレーションエンティティ
- remult.repo(エンティティ名).insert(): 追加。
- remult.repo(エンティティ名): エンティティのCRUD操作。
-
以下のような表示になり、タイトルやカテゴリを設定して登録できることを確認します。
- 以上です。
まとめ
- RemultのAPIやドキュメントの自動生成とSveltekitの洗練さによって、データやビジネスロジックに集中でき、より高速で堅牢なアプリケーションを構築できると感じました。
Discussion