Vapor で MySQL と接続して JSON レスポンス
Vapor に慣れるために Hello World より少し進めてみる。今回はデータベースに接続して データを POST メソッドにて保存、JSON レスポンスを返すまでをやってみる。
とはいっても、前述の状態を実現するだけなら簡単で Vapor Toolbox にて新規プロジェクトを作成する際に Fluent を利用するようにすることによりマイグレーション用のファイルやコントローラなどが自動で生成できる。
さっそく新規プロジェクトを作成。
simple-api
というプロジェクト名で作ってみる。プロジェクト生成時点で Todo というシンプルなテーブルを使うアプリケーションが組み込まれている。この Todo のモデルやコントローラなどのコードを眺めているとなんとなくどんな風に設定していくのかがわかるかもしれない。
$ vapor new simple-api
Cloning template...
name: simple-api
Would you like to use Fluent (ORM)? (--fluent/--no-fluent)
y/n> y
fluent: Yes
db: MySQL
Would you like to use Leaf (templating)? (--leaf/--no-leaf)
y/n> y
leaf: Yes
Generating project files
...
環境変数でデータベースの接続情報をセットアップ
生成されたコードを見ると、データベースの接続方法がなんとなくわかる
Sources/App/configure.swift
import NIOSSL
import Fluent
import FluentMySQLDriver
import Leaf
import Vapor
public func configure(_ app: Application) async throws {
...
app.databases.use(DatabaseConfigurationFactory.mysql(
hostname: Environment.get("DATABASE_HOST") ?? "localhost",
port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? MySQLConfiguration.ianaPortNumber,
username: Environment.get("DATABASE_USERNAME") ?? "vapor_username",
password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password",
database: Environment.get("DATABASE_NAME") ?? "vapor_database"
), as: .mysql)
app.migrations.add(CreateTodo())
...
}
これはもしや .env
ファイルから環境変数を設定することもできるかなとドキュメントを見たら、できそうだった。
ということで設定してみる。データベースは vapor-sample-1
という名前で CREATE SCHEMA vapor-sample-1 DEFAULT CHARACTER SET utf8mb4 ;
にてとりあえず作成しておいた。
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USERNAME=XXXXXXXX
DATABASE_PASSWORD=XXXXXXXX
DATABASE_NAME=vapor-sample-1
ローカルの接続情報を設定した。MySQL 自体はローカルの Docker コンテナとして動かしている。
マイグレーション
データベースはまだ何のテーブルもない状態だが、 fluent
にはマイグレーション機能があり、初期テーブルの作成がコマンドラインにて以下のように行うことが出来る。
$ swift run App migrate
Building for debugging...
[9/9] Linking App
Build complete! (2.10s)
Migrate Command: Prepare
The following migration(s) will be prepared:
+ App.CreateTodo on <default>
Would you like to continue?
y/n> y
[ INFO ] [Migrator] Starting prepare [database-id: mysql, migration: App.CreateTodo]
[ INFO ] [Migrator] Finished prepare [database-id: mysql, migration: App.CreateTodo]
Migration successful
これで、データベース上にテーブル todo が作成された。
Todo コントローラの中身
サンプルコントローラを見ると、index
、create
、delete
というルートが作られたのがわかる
Sources/App/Controllers/TodoController.swift
import Fluent
import Vapor
struct TodoController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let todos = routes.grouped("todos")
todos.get(use: self.index)
todos.post(use: self.create)
todos.group(":todoID") { todo in
todo.delete(use: self.delete)
}
}
@Sendable
func index(req: Request) async throws -> [TodoDTO] {
try await Todo.query(on: req.db).all().map { $0.toDTO() }
}
@Sendable
func create(req: Request) async throws -> TodoDTO {
let todo = try req.content.decode(TodoDTO.self).toModel()
try await todo.save(on: req.db)
return todo.toDTO()
}
@Sendable
func delete(req: Request) async throws -> HTTPStatus {
guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else {
throw Abort(.notFound)
}
try await todo.delete(on: req.db)
return .noContent
}
}
アプリを起動して、一つ todo レコードを生成してみる
アプリの起動は $ swift run
にて行う。ちょっと REST クライアントで POST メソッドをたたいてみよう。My Title
というタイトルを持ったレコードを一つ作成してみる。
POST http://localhost:8080/todos HTTP/1.1
content-type: application/json
{
"title": "My Title",
}
すると、うまく設定されていれば以下のようなレスポンスが返される。無事レコードもデータベース上に作成されており、一山超えた満足感がある。
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
content-length: 64
connection: close
date: Tue, 27 Aug 2024 13:26:02 GMT
{
"id": "4587E60A-E9ED-43EB-B692-294124BB4E13",
"title": "My Title"
}