LoginSignup
18
9

More than 5 years have passed since last update.

VaporとMySQLでToDoAPIを作ってみた!

Last updated at Posted at 2016-12-09

はじめに

こんにちは。こんばんは。スマートテック・ベンチャーズ 10日目STVオタク担当の @nnsnodnb です。
今後サーバサイドも全部Swiftできるようになるために生まれてきたのではないかと思われている(少なくとも自分の中では)
サーバサイドSwiftについてまた触ってみました。Vaporで行こうと思います。
Kitura+MySQL が上手くできなかったいい感じにできた人いらしたら教えてくださいエロい人...

環境

  • macOS Sierra 10.12.1 (個人所有のMacが修理中のため知人のMacを借りてます)
  • Xcode 8.1
  • Swift 3.0.1
  • mysql Ver 14.14 Distrib 5.7.16, for osx10.12 (x86_64) using EditLine wrapper

準備

MySQL準備

Homebrewでインストールを進めます。

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew install mysql
$ sudo mysql.server start

あとはDB作成等

Vapor準備

$ curl -sL check.vapor.sh | bash
✅  Compatible
$ curl -sL toolbox.vapor.sh | bash
✅  Compatible
Downloading...
Compiling...
Installing...
Vapor Toolbox v1.0.3 Installed
Use vapor --help and vapor <command> --help to learn more.

プロジェクトファイル準備

$ vapor new vaporTodoApi
Cloning Template [Done]

スクリーンショット 0028-12-04 午前3.55.04.png

$ tree .
.
├── Config
│   ├── app.json
│   ├── clients.json
│   ├── crypto.json
│   ├── droplet.json
│   ├── production
│   │   └── app.json
│   └── servers.json
├── Localization
│   ├── default.json
│   ├── en-US.json
│   ├── es-US.json
│   ├── nl-BE.json
│   └── nl-NL.json
├── Package.swift
├── Procfile
├── Public
│   ├── images
│   │   └── vapor-logo.png
│   └── styles
│       └── app.css
├── README.md
├── Resources
│   └── Views
│       ├── base.leaf
│       └── welcome.leaf
├── Sources
│   └── App
│       ├── Controllers
│       │   └── PostController.swift
│       ├── Models
│       │   └── Post.swift
│       └── main.swift
├── app.json
└── license

Vaporコマンドで実行するとここまで自動的にしてくれるのはいいですね!

ちなみにXcodeで作業したいときはnewしたあとに以下のようにやればできます。

$ vapor xcode -n
$ vapor xcode mysql -y

自動的にXcodeが開くはずです。

MySQL接続設定

Config/mysql.json を作成して情報を付加する

mysql.json
{
    "host": "127.0.0.1",
    "user": "root",
    "password": "",
    "database": "todo",
    "encoding": "utf8"
}

DATABASEは todo という名前で新規作成しておいてください。
ユーザ等は各自適当に変更お願いします:muscle:

Swift Package Manager設定

Package.swift
import PackageDescription

let package = Package(
    name: "vaporTodoApi",
    dependencies: [
        .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 1),
        .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 1, minor: 1)  // 追加
    ],
    exclude: [
        "Config",
        "Database",
        "Localization",
        "Public",
        "Resources",
        "Tests",
    ]
)
$ vapor build --mysql
Fetching Dependencies [Done]
Building Project [Done]

Xcodeで作業されている方は基本的に Build & Run で大丈夫かと思います。
もしダメだったら上のコマンドを叩いてください:bow:

サンプルソースコード

Sources/App/main.swift
import Vapor
import VaporMySQL

let drop = Droplet(
  preparations: [Task.self],
  providers: [VaporMySQL.Provider.self]
)

drop.resource("tasks", TaskController())

drop.run()

もともとあるかと思われる Post.swift を変更するなりなんなり

Sources/App/Model/Task.swift
import Vapor
import Fluent
import Foundation

final class Task: Model {
    var id: Node?
    var title: String
    var rank: String

    init(title: String, rank: String) {
        self.title = title
        self.rank = rank
    }

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

    func makeNode(context: Context) throws -> Node {
        return try Node(node: [
            "id": id,
            "title": title,
            "rank": rank
        ])
    }
}

extension Task {
    public convenience init?(from title: String, rank: String) throws {
        self.init(title: title, rank: rank)
    }
}

extension Task: Preparation {
    static func prepare(_ database: Database) throws {
      try database.create("tasks") { tasks in
          tasks.id()
          tasks.string("title")
          tasks.string("rank")
      }
    }

    static func revert(_ database: Database) throws {
        try database.delete("tasks")
    }
}

こちらも、もともとあるかと思われる PostViewController.swift を変更するなりなんなり

Sources/App/Model/TaskViewController.swift
import Vapor
import HTTP

final class TaskController: ResourceRepresentable {
    func index(request: Request) throws -> ResponseRepresentable {
        return try Task.all().makeNode().converted(to: JSON.self)
    }

    func create(request: Request) throws -> ResponseRepresentable {
        var task = try request.post()
        try task.save()
        return task
    }

    func show(request: Request, task: Task) throws -> ResponseRepresentable {
        return task
    }

    func delete(request: Request, task: Task) throws -> ResponseRepresentable {
        try task.delete()
        return JSON([:])
    }

    func clear(request: Request) throws -> ResponseRepresentable {
        try Task.query().delete()
        return JSON([])
    }

    func update(request: Request, item task: Task) throws -> ResponseRepresentable {
        let new = try request.post()
        var task = task
        task.title = new.title
        task.rank = new.rank
        try task.save()
        return task
    }

    func replace(request: Request, task: Task) throws -> ResponseRepresentable {
        try task.delete()
        return try create(request: request)
    }

    func makeResource() -> Resource<Task> {
        return Resource(
            index: index,
            store: create,
            show: show,
            replace: replace,
            modify: update,
            destroy: delete,
            clear: clear
        )
    }
}

extension Request {
    func post() throws -> Task {
        guard let json = json else {
            throw Abort.badRequest
        }
        return try Task(node: json)
    }
}

今回はDBのテーブル構成は id , title , rank というフラグのない謎構成です。

ソースコードが書けたらとりあえず実行してみよか?

$ vapor build --mysql
$ vapor run

Xcodeの方はそのまま Build & Run で行けるかと思います。

Runが通れば自動的にDBにテーブルを作成してくれます。

404ページ

スクリーンショット 2016-12-07 午前1.19.38.png

404ページは始めからパッケージに同梱されているらしいです!感謝:pray:

HTTPメソッドアクセスについて

GET

GETリクエストについては index()show() が呼ばれます。

index()

func index(request: Request) throws -> ResponseRepresentable {
    return try Task.all().makeNode().converted(to: JSON.self)
}

見ての通り全てを取り出してJSON形式にしてレスポンスしてくれます。

スクリーンショット 2016-12-07 午前1.24.44.png

$ curl http://127.0.0.1:8080/tasks

show()

func show(request: Request, task: Task) throws -> ResponseRepresentable {
    return task
}

詳細情報取得みたいなやつです。

スクリーンショット 2016-12-07 午前1.27.19.png

$ curl http://127.0.0.1:8080/tasks/id

id のところはデータのIDです。

POST

POSTリクエストは create() が呼ばれます。

func create(request: Request) throws -> ResponseRepresentable {
    var task = try request.post()
    try task.save()
    return task
}

POSTで受け取ったデータをインサートしてるだけですね。Realmとか使ってる人なら見覚えがあるかと...あとはNoSQL使いの皆様とか

スクリーンショット 2016-12-07 午前1.32.52.png

$ curl http://127.0.0.1:8080/tasks -X POST -d '{"title": "hoge", "rank": "中"}' -H "Content-Type: application/json" -H "Accept: application/json"

PUT

PUTリクエストは update() が呼ばれます。

func update(request: Request, item task: Task) throws -> ResponseRepresentable {
    let new = try request.post()
    var task = task
    task.title = new.title
    task.rank = new.rank
    try task.save()
    return task
}

これについてはちょっと仕様がわからないんですが、古いデータを削除して新しくインサートしているような動作をしているようです。
もしかしたら私のやり方が間違っているのかもしれませんが...

スクリーンショット 2016-12-07 午前1.38.29.png

$ curl http://127.0.0.1:8080/tasks/id -X PUT -d '{"title": "あっぷでーと", "rank": "低"}' -H "Content-Type: application/json" -H "Accept: application/json"

DELETE

DELETEリクエストは delete() が呼ばれます。

func delete(request: Request, task: Task) throws -> ResponseRepresentable {
    try task.delete()
    return JSON([:])
}

Vaporの中身をみていないのでなんともわかりませんが、 id でfilterして削除しているのではないかと思います。

スクリーンショット 2016-12-07 午前1.40.57.png

$ curl http://127.0.0.1:8080/tasks/id -X DELETE

Clearについて

clear() なんですが、呼び方がわからないのでとりあえず放置しますが、(知ってる人教えてください)
呼ぶと全てのキーを削除してしまうので下みたいにならないようにしましょうねw

syazai_kaiken.png

補足

func makeResource() -> Resource<Task> {
    return Resource(
        index: index,
        store: create,
        show: show,
        replace: replace,
        modify: update,
        destroy: delete,
        clear: clear
    )
}

makeResource() に書かれているようなキーの右側を任意のメソッド名に変更してあげると思い通りにしてくれます。

最後に

ざっと、デフォルトでつぎ込まれている機能のみ紹介しましたが、 filter なども実装されているとのことなのでGETパラメータでほげほげできそうですね。

また、今回使用したソースコードはGitHubに公開しています。
よかったら見てみてください!


明日の11日目は、また私の担当になります。
モバイルバックエンドについての記事になります。主にサーバ周りの話をしていきます。
インフラもちょっと話したいです。お兄さんたちに怒られない程度に...

18
9
1

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
18
9