【サーバーサイドSwift】VaporでMySQLと接続してみた【Vapor v0.16.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](https://vapor.github.io/documentation/getting-started/hello-world.html)」のセクションで、ローカルでサーバーを立てることができればOKです。

# MySQLを起動
次に、あらかじめローカル環境でMySQLを起動させておきます。
[こちら](https://github.com/vapor/mysql)を参考に、まずはmacにMySQLをインストールし、ローカルで起動させます。

```shell-session
brew install mysql
brew link mysql
mysql.server start
```

# MySQLにデータベースを作成

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

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

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

```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`に

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

を追加します。

```swift: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のプロバイダーを追加します。

```swift: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`を追加しました。

# いったん動かしてみる

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

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

```shell-session
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のライブラリが見つからないと怒られてしまいました。↓

```shell-session
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`に３点追記します。

```swift: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")
    }
}
```

- **追加１**：Nodeについてはまだきちんと分かっていないのですが、Docsだとidも記述されていたので、足しています。（どちらが正しいのか、、）
- **追加２**：これを追加し、後述のDropletへの追記をすることで、`vapor run`した時に、MySQLへ`users`テーブルが作られます。
- **追加３**：こちらは、`vapor run prepare --revert`と打つことで呼ばれ、`users`テーブルを削除します。（手元でこの挙動を確認できませんでした。。）

## Dropletにpreparationsを追加する

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

```swift: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`でサーバー立ち上げます。

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

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

```mysql
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.swift`の`store`メソッドが呼ばれます。
（vapor newで作られるデフォルトのアプリケーションだとそうなっているので、それをそのまま使います。）

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

```swift: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で行う場合は、次のような感じです。

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

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

# データを削除する

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

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

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

```swift: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を投げてみると、データが削除されると思います。

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

# データを更新する

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

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

```swift: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を送ると一応データの更新ができます。

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

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

```shell-session
Server error: dispatch(HTTP.ParserError.streamEmpty)
```

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

# モデルのデータ一覧取得

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

```swift: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です。

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

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


# 終わりに

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

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