0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Remult + SvelteKitが良さげ

Posted at
  • 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.jsoncompilerOptionsに以下を追加します。
    • Remultではデコレーターベースでエンティティを書いていくため。
"experimentalDecorators": true

APIルートの作成

  • 次に以下のコマンドで、APIルートを作成するためのディレクトリおよびファイルを作成します。
    • Sveltekitではroutes配下に+serverファイルを作成することでAPIルートを定義できます。
# 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;
}
  • 上記のコードの簡単な説明は以下です。

    • @Entity('テーブル名'): DBのテーブルに対応します。
    • allowApiCrud: エンティティに対してCRUD操作を許可するか。
      • READだけやDELETEだけ等、細かく設定できる。
      • 他のオプションはこちら
    • @Field.タイプ: データの型。
    • Validators: API側でのバリデーション。
  • 次に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ではこうしたデータベース間の関連性をシンプルなデコレータで簡単に定義できます。
      • 他のリレーションはこちら

エンティティの登録

  • 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が表示されていることを確認します。

img

  • 以下で自動生成されたAPIは単純なCRUDだけでなくソートやページング等も備えていて、また自動生成されたOpen API Documentはスキーマやexampleの定義も行なっていることを確認します。

img

img

  • 以下でリクエストのテストも正常に行えることを確認します。

img

img

データ

  • 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(): 追加。
  • 以下のような表示になり、タイトルやカテゴリを設定して登録できることを確認します。

img

  • 以上です。

まとめ

  • RemultのAPIやドキュメントの自動生成とSveltekitの洗練さによって、データやビジネスロジックに集中でき、より高速で堅牢なアプリケーションを構築できると感じました。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?