はじめに
こんにちは。こんばんは。スマートテック・ベンチャーズ 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]
$ 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
を作成して情報を付加する
{
"host": "127.0.0.1",
"user": "root",
"password": "",
"database": "todo",
"encoding": "utf8"
}
DATABASEは todo
という名前で新規作成しておいてください。
ユーザ等は各自適当に変更お願いします
Swift Package Manager設定
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
で大丈夫かと思います。
もしダメだったら上のコマンドを叩いてください
サンプルソースコード
import Vapor
import VaporMySQL
let drop = Droplet(
preparations: [Task.self],
providers: [VaporMySQL.Provider.self]
)
drop.resource("tasks", TaskController())
drop.run()
もともとあるかと思われる Post.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
を変更するなりなんなり
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ページ
404ページは始めからパッケージに同梱されているらしいです!感謝
HTTPメソッドアクセスについて
GET
GETリクエストについては index()
と show()
が呼ばれます。
index()
func index(request: Request) throws -> ResponseRepresentable {
return try Task.all().makeNode().converted(to: JSON.self)
}
見ての通り全てを取り出してJSON形式にしてレスポンスしてくれます。
$ curl http://127.0.0.1:8080/tasks
show()
func show(request: Request, task: Task) throws -> ResponseRepresentable {
return task
}
詳細情報取得みたいなやつです。
$ 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使いの皆様とか
$ 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
}
これについてはちょっと仕様がわからないんですが、古いデータを削除して新しくインサートしているような動作をしているようです。
もしかしたら私のやり方が間違っているのかもしれませんが...
$ 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して削除しているのではないかと思います。
$ curl http://127.0.0.1:8080/tasks/id -X DELETE
Clearについて
clear()
なんですが、呼び方がわからないのでとりあえず放置しますが、(知ってる人教えてください)
呼ぶと全てのキーを削除してしまうので下みたいにならないようにしましょうねw
補足
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日目は、また私の担当になります。
モバイルバックエンドについての記事になります。主にサーバ周りの話をしていきます。
インフラもちょっと話したいです。お兄さんたちに怒られない程度に...