【サーバーサイドSwift】VaporでMySQLと接続してみた【Vapor v0.16.0】

  • 14
    いいね
  • 0
    コメント

環境

バージョン
Swift DEVELOPMENT-SNAPSHOT-2016-07-25-a
Xcode 8.0 beta 3 (8S174q)
Mac OSX 10.11.5(15F34)
Vapor v0.16.0
Vapor Toolbox v0.8.0

全てMacのローカル環境で行っています。

Vaporのプロジェクトを作成

今回は、公式DocsのサンプルアプリケーションにMySQL接続部分を追加してみたいので、
以下を参考にHelloプロジェクトを作成します。

https://vapor.github.io/documentation/
Docsの通りに行えば問題なくできるかと思いますので、ここの詳細は省きます。
「GETTING STARTED」の「Hello, World」のセクションで、ローカルでサーバーを立てることができればOKです。

MySQLを起動

次に、あらかじめローカル環境でMySQLを起動させておきます。
こちらを参考に、まずはmacにMySQLをインストールし、ローカルで起動させます。

brew install mysql
brew link mysql
mysql.server start

MySQLにデータベースを作成

MySQLにデータベースをあらかじめ用意しておきます。
今回はvaporという名前のデータベースを作成します。

データベースのconfigファイルを作成

プロジェクト直下のConfigフォルダ内に自分のMySQLの環境に合わせて次のような設定ファイルをmysql.jsonという名前で作成します。

Config/mysql.json
{
    "host": "localhost",
    "user": "root",
    "password": "",
    "database": "vapor"
}

https://vapor.github.io/documentation/guide/provider.html#config

プロバイダーの追加

https://vapor.github.io/documentation/guide/provider.html
上記を参考に、mysql-providerをプロジェクトに追加します。

Package.swiftにパッケージを追加する

Package.swift

.Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 0, minor: 4)

を追加します。

Package.swift
import PackageDescription

let package = Package(
    name: "Hello",
    dependencies: [
        .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 0, minor: 16),
        .Package(url: "https://github.com/vapor/vapor-mustache.git", majorVersion: 0, minor: 11),
        .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 0, minor: 4)
    ],
    exclude: [
        "Config",
        "Database",
        "Localization",
        "Public",
        "Resources",
        "Tests",
    ]
)

Dropletにプロバイダーを追加

main.swiftに次のようにMySQLのプロバイダーを追加します。

main.swift
import Vapor
import VaporMustache
import VaporMySQL // ←追加
import HTTP


/**
    Droplets are service containers that make accessing
    all of Vapor's features easy. Just call
    `drop.serve()` to serve your application
    or `drop.client()` to create a client for
    request data from other servers.
*/
let drop = Droplet(providers: [VaporMustache.Provider.self, VaporMySQL.Provider.self])
// ↑VaporMySQL.Provider.selfを追加

// 以下略

import VaporMySQLとDropletの引数のprovidersにVaporMySQL.Provider.selfを追加しました。

いったん動かしてみる

ここまでで、いったんビルドして、サーバーを動かしてみます。

プロジェクト直下で以下のようにビルドします。

swift build -Xswiftc -I/usr/local/include/mysql -Xlinker -L/usr/local/lib

以降、上記コマンドでビルドします。

<追記:2016/08/16>

vapor build --mysql

このコマンドでもビルドできるようなので、こちらのほうが便利ですね。
<追記ここまで>

次に、vapor runでサーバーを立ち上げます。
上手く立ち上がれば、いったんOKです。

ちなみに、、
vapor buildや単純にswift buildだとmysqlのライブラリが見つからないと怒られてしまいました。↓

Linking ./.build/debug/App
ld: library not found for -lmysqlclient for architecture x86_64
<unknown>:0: error: link command failed with exit code 1 (use -v to see invocation)

モデルをMySQLと紐付ける

モデルにテーブルの作成・削除ロジックを追加する

models/User.swiftに3点追記します。

models/User.swift
import Vapor
import Fluent

final class User: Model {
    var id: Node?
    var name: String

    init(name: String) {
        self.name = name
    }

    init(node: Node, in context: Context) throws {
        id = try node.extract("id")
        name = try node.extract("name")
    }

    func makeNode() throws -> Node {
        return try Node(node: [
            "id": id, // ←追加! (1)
            "name": name
        ])
    }

    static func prepare(_ database: Database) throws {
        // ↓追加! (2)
        try database.create("users") { users in
            users.id()
            users.string("name")
        }
    }

    static func revert(_ database: Database) throws {
        // ↓追加! (3)
        try database.delete("users")
    }
}
  • 追加1:Nodeについてはまだきちんと分かっていないのですが、Docsだとidも記述されていたので、足しています。(どちらが正しいのか、、)
  • 追加2:これを追加し、後述のDropletへの追記をすることで、vapor runした時に、MySQLへusersテーブルが作られます。
  • 追加3:こちらは、vapor run prepare --revertと打つことで呼ばれ、usersテーブルを削除します。(手元でこの挙動を確認できませんでした。。)

Dropletにpreparationsを追加する

main.swiftのDropletの初期化部分にpreparationsを追加します。
これがないと、先ほどモデルに書いたprepareメソッドが呼ばれず、テーブルの作成がされません。

main.swift
import Vapor
import VaporMustache
import VaporMySQL
import HTTP


/**
    Droplets are service containers that make accessing
    all of Vapor's features easy. Just call
    `drop.serve()` to serve your application
    or `drop.client()` to create a client for
    request data from other servers.
*/
let drop = Droplet(
    preparations: [User.self], // ←追加
    providers: [VaporMustache.Provider.self, VaporMySQL.Provider.self]
)

// 以下略

余談ですが、引数の順序として、providersよりpreparationsの方が先に記述しないと怒られます。

https://vapor.github.io/documentation/guide/droplet.html#initialization-1

これで、前述のコマンドでビルドし、
vapor runでサーバー立ち上げます。

$ vapor run
Running Hello...
No command supplied, defaulting to serve...
Preparing User
Prepared 'User'
Database prepared

こんな感じでusersテーブルが作成されます。

mysql> use vapor
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-----------------+
| Tables_in_vapor |
+-----------------+
| fluent          |
| users           |
+-----------------+
2 rows in set (0.00 sec)

mysql> show columns from users;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

データを追加する

とりあえず実用性は考えずに、クエリをつけてPOSTすればデータを追加できるようなものを作ってみます。

http://localhost:8080/usersに対して、POSTをすると、
Controllers/UserController.swiftstoreメソッドが呼ばれます。
(vapor newで作られるデフォルトのアプリケーションだとそうなっているので、それをそのまま使います。)

今回は手っ取り早くstoreメソッド内に、データの保存を行うコードを追加します。

Controllers/UserController.swift
// 略

    func store(request: Request) throws -> ResponseRepresentable {
        // 追加部分
        if let name = request.data["name"].string {
            user = User(name: name)
            try user.save()
        }
        // 追加部分ここまで
        return try JSON([
            "controller": "UserController.store"
        ])
    }

// 略

nameというパラメータを受け取って、Userモデルのインスタンスを作成し、保存しています。

これで再度ビルドし、vapor runでサーバーを立ち上げます。

それでは、実際にPOSTをしてみましょう。
方法はなんでもいいですが、curlで行う場合は、次のような感じです。

curl http://localhost:8080/users -X POST -d "name=hoge"

これで、データが保存されていればOKです。

データを削除する

こちらもまずは実用性は考えずに、単純にDELETEメソッドでリクエストするとデータが削除されるものを作ります。

http://localhost:8080/usres/{id}にDELETEメソッドでリクエストすると、Controllers/UserController.swiftdestroyメソッドが呼ばれます。
(vapor newで作られるデフォルトのアプリケーションだとそうなっているので、そのまま使います。)

このdestroyメソッドにデータを削除するコードを追加します。

Controllers/UserController.swift
// 略
    func destroy(request: Request, item user: User) throws -> ResponseRepresentable {
        //User is ResponseRepresentable by proxy of JsonRepresentable
        try user.delete() // ←追加
        return user
    }
// 略

こちらも、curlでDELETEを投げてみると、データが削除されると思います。

↓ID:1のデータを削除
curl http://localhost:8080/users/1 -X DELETE

データを更新する

データの更新については、正しいやり方ではない気しかしないですが、とりあえずできたやり方を残します。

ここまでの流れから分かるかと思いますが、更新はPUTメソッドでのリクエストでControllers/UserController.swiftupdateメソッドが呼ばれます。

Controllers/UserController.swift
// 略
    func update(request: Request, item user: User) throws -> ResponseRepresentable {
        if let name = request.data["name"].string {
            var user = try User(node: user.makeNode())
            user.name = name
            try user.save()
            return user.makeJSON()
        }
        return user.makeJSON()
    }
// 略

ちょっと回りくどいですね。。
「とりあえず感」がすごいですが、これでcurlでPUTを送ると一応データの更新ができます。

curl http://localhost:8080/users/1 -X PUT -d "name=fuga"

ただ、vaporのコンソールを見ていると、

Server error: dispatch(HTTP.ParserError.streamEmpty)

というエラーが出ているので、やはり何か違うのでしょう、、
引き続きコードやDocs読んで試していきます。

モデルのデータ一覧取得

もはやどこでもいいのですが、とりあえずhttp://localhost:8080/usersGETでのアクセス(ブラウザでアクセス)して一覧をjsonで表示させるようなものにしました。

Controllers/UserController.swift
    func index(request: Request) throws -> ResponseRepresentable {
        let users = try User.query().all()
        return try JSON(users)
    }

try User.query().all()で取得できるというところが分かれば、使いどころはここでなくても大丈夫です。

Fluent

ここまで、MySQLへのデータの操作を簡単に見てきましたが、データの操作にはFluentが使われています。
FluentはSwift製のORMです。

これまでの使い方以外にもfilterrelationなども扱えるので、いろいろ触ってみると面白そうです。

https://github.com/vapor/fluent
https://vapor.github.io/documentation/fluent/query.html

終わりに

今回は、クイックにMySQLとの接続を試してみたかったので、書き方や細かいところは全く考えていません。
というより、単純に分からないところが多いというのが実際のところなので、知見のある方、是非ご教授願いたいです :raised_hands:

また、Docsがかなり分かりやすい(分かりやすくなってきた)ので、これからいろいろやってみたいと思います。