Server Side Swift

Vapor 4 初探:實作一個待辦事項 App 了解這個 Swift 框架

在這系列的教學文章中,我會為大家介紹一些 Vapor 提供的功能,並一起構建一個待辦事項 App。這篇文章主要會介紹什麼是 Vapor,深入了解這個框架,並介紹一些基礎知識。
Vapor 4 初探:實作一個待辦事項 App 了解這個 Swift 框架
Vapor 4 初探:實作一個待辦事項 App 了解這個 Swift 框架
In: Server Side Swift

本篇原文(標題:Getting Started With Vapor 4 —Learn By Building a Todo Application)刊登於作者 Medium,由 Fernando Moya de Rivas 所著,並授權翻譯及轉載。

簡介

老實說,我構建 Web App 的經驗不多,但我是 iOS/macOS 開發者,對 Swift 非常了解。我有聽說過 Vapor,但未深入研究過這個框架。讀過 Vapor 的文檔、並動手試試之後,我感到十分開心。

在這系列的教學文章中,我會為大家介紹一些 Vapor 提供的功能,並一起構建一個待辦事項 App。這篇文章主要會介紹什麼是 Vapor,深入了解這個框架,並介紹一些基礎知識。

你可以在我的 GitHub 儲存庫上參考完整的程式碼。

Vapor 概覽

Vapor 是什麼?

Vapor 是一個基於 Swift 語言的伺服器 (server) 端框架,提供一系列的工具來幫助我們輕鬆地從零開始創建 Web App,或是定義一個 API 來支援我們的 mobile 客戶端。其他編程語言也有相應的 Web App 框架:

為什麼要用 Vapor?

經過一番研究,我發現 Swift 社群都已經接受了 Vapor。這個框架十分成熟,提供了大量的可能性,同時也易於使用;這正是我的工作目標!我們只需要做一點配置,就可以立即啟動並運行 App。

加上,這個框架是以 Swift 語言編寫的,這不但是我熟悉的語言,而且也可以在 Xcode 和終端機 (Terminal) 中使用。另外,Vapor 可以支援 macOS 和 Linux,甚至可以創建一個在 Docker中運行的圖像。

Vapor 是唯一的 Swift 伺服器端框架嗎?

不是。我們還有其他選擇,像是 KituraPerfect,但社群對兩者的接受程度都不及 Vapor 。

建立我們的第一個 App:待辦事項 App

要了解什麼是 Vapor,最好的方法就是構建一個範例 App。讓我們一起構建一個簡單的範例,看看 Vapor 的操作。我們會構建一個待辦事項 App,並使用終端機、Postman、或任何其他 API 客戶端來進行測試。

安裝

如果你還沒有安裝 Vapor,可以先執行 brew install vapor。你需要先安裝 Homebrew,並把 Swift 更新到版本 5.2 以上。

或者,如果你是使用 Linux 的話,可以參考這個網頁的步驟。

新專案

讓我們執行 vapor new <project_name>,來從模板創建新專案。在預設情況下,Vapor 會詢問我們是否要使用 Leaf 和/或 Fluent,我們稍後會詳細介紹這個設置。兩個問題我們都填 n,我們也可以執行以下命令來選擇 -n

$ vapor new TodoApp -n

專案結構

讓我們先看看專案的結構:

.
├── Dockerfile
├── Package.swift
├── Sources
│   ├── App
│   │   ├── Controllers
│   │   ├── configure.swift
│   │   └── routes.swift
│   └── Run
│       └── main.swift
├── Tests
│   └── AppTests
│       └── AppTests.swift
└── docker-compose.yml

我們需要先了解以下這三個檔案:

  • main.swift:這是我們的 Application 被定義的地方,這個檔案應盡量維持不變。
  • configure.swift:這個檔案適合讓我們 register service、配置數據庫 (database)、定義一些 middleware、或啟動 queue 系統等。我們還可以在這裡更改 HTTP 配置(hostname、port 等)。
  • routes.swift:這個檔案是用來定義 App 的 endpoint 和 routing。在現實的 App 中,我們需要處理最少十個 endpoint,因此,我們會想在 Controllers 中把 endpoint 分組。

執行 App

在預設情況下,Vapor 會從模板創建新專案,所以我們已經會有幾個定義好的 endpoint(你可以在 routes.swift 中確認一下)。

我們有兩個方法去執行 App:

  • 在 Xcode 執行:在專案資料夾內執行 vapor xcode,然後按 ⌘R。
  • 在終端機執行:執行 vapor run serve

在預設情況下,我們的 App 會在 127.0.0.1:8080localhost:8080 中運行。我們可以利用以下程式碼輕鬆進行測試:

$ curl localhost:8080/hello
Hello, world!%

待辦事項列表:CRUD (Create, Read, Update, Delete) 操作

現在,我們的 App 與待辦事項列表有關的地方,就只有專案的名稱。所以接下來,讓我們在 App 客戶端創建一個新的待辦事項列表。

Fluent 和 SQLite

Vapor 支援 Fluent ORM,它支援 SQLite、MySQL、PostgreSQL 和 MongoDB。因為我們的範例 App 非常簡單,我們會使用支援記憶體資料庫 (in-memory database) 的 SQLite driver。在現實 App 中,我們可能會想使用一個持久數據庫,但在這篇教學中,記憶體資料庫就足夠了。

要配置 Fluent 和 SQLite,讓我們到 Package.swift 添加 Fluent 和 FluentSQLiteDriver。程式碼看起來應該是這樣的:

我們不需要更改檔案的其餘部分。最後,在 configure.swift 中添加以下內容:

import Vapor
import Fluent
import FluentSQLiteDriver
public func configure(_ app: Application) throws {
  app.databases.use(.sqlite(.memory), as: .sqlite)
  try routes(app)
}

Model 和 Migration

Model 是 Fluent 理解的 Entity,而 Migration 是負責創建或更新數據庫表格 (table) 的程式碼,就像是所有更改的記錄冊或版本控制。因此,添加它們的順序十分重要。

讓我們設置 Model 並定義 TodoList 的外觀:

import Fluent

extension FieldKey {
  static let name: FieldKey = "name"
}

final class TodoList: Model {
  static let schema = "todo-lists"
  @ID(key: .id)
  var id: UUID?
  @Field(key: .name)
  var name: String
}

現時,TodoList 只有一個 name。我們使用了 @ID@Field,前者指明了表格查找 key 的 field,而後者就是用來定義表格列 (column) 的名稱。也就是說,Model 定義數據庫表格,同時也定義了 schema 來定義其名稱。

定義好 Model 之後,我們只需要創建一個 Migration 來構建表格就可以了:

import Fluent

struct CreateTodoListMigration: Migration {
  func prepare(on database: Database) -> EventLoopFuture<Void> {
    database
      .schema(TodoList.schema)
      .id()
      .field(.name, .string, .required)
      .create()
  }
  
  func revert(on database: Database) -> EventLoopFuture<Void> {
    database
      .schema(TodoList.schema)
      .delete()
  }
}

從上面的程式碼可見,Migration 創建的表格會有兩列:idname。我們要確定那些 nameModel 內定義的名稱相同。如果我們要還原 (revert) 這個 migration,操作就會完全相反,也就是會刪除表格。

最後,在 configure.swift register 我們的 migration:

// after registering your database...
app.migrations.add(TodoMigration(), to: .sqlite)
try app.autoMigrate().wait()
備註:在使用記憶體資料庫時,我們需要使用 Auto-migration。

Routes

接下來,我們可以開始定義一些 endpoint。我們會考慮兩個操作:

  • GET:我們會回傳所有可用的 TodoList
  • POST:我們會讓客戶端創建一個新的 TodoList

我們會在 routes.swift 中定義 endpoint。打開檔案,我們會看到一個以 Application 為引數 (argument) 的 func routes(_:),這個函式是在 configure.swift 調用的,用來定義所有 App 允許的 route。

將以下程式碼放入其中:

app.get("todo-lists") { req in
  TodoList.query(on: req.db)
    .all()
    .encodeResponse(for: req)
}

從以上的程式碼片段可以看到:

  • 它定義了一個 GET route/endpoint,而其 path 是 todo-lists
  • 在被調用的時候,我們會向 database 查詢,並回傳所有可用的 TodoList
  • 最後,我們會回傳 [TodoList] 陣列為 response。

在進行下一步之前,我們要先解決一個編譯器錯誤 (compiler error)。因為如果要將 Entity 編碼為 JSON response,以從 request 中解碼,Entity/Model 就需要遵從 Content

import Vapor
extension TodoList: Content { }

每個我們定義的新 endpoint 都需要回傳一個 EventLoopF​​uture,這只是一個異步 (asynchronous) 操作的包裝器,在 endpoint 被調用時執行。Vapor 是一個建基於 SwiftNIO 的框架。

備註:最新版本的 Vapor 支援 async/await,也就是說,前面定義的 endpoint 也可以寫成這樣:

app.get("todo-lists") { req in
  try await TodoList
    .query(on: req.db)
    .all()
    .get()
}

不過,考慮到兼容性的問題,我們還是會使用 EventLoopF​​uture

同樣地,我們可以定義一個 POST 操作,來創建一個 TodoList

app.post("todo-lists") { req in
  try req.content
    .decode(TodoList.self)
    .save(on: req.db)
    .transform(to: Response(status: .created))
}

在這個情況下,我們會使用 post(_:use:) 來定義一個 POST 操作,並在當中:

  • 嘗試從 request 內容中解碼 TodoList
  • 把它儲存在數據庫;
  • 把結果轉換為一個 HTTP 201 Created response。

讓我們試試吧:

$ curl localhost:8080/todo-lists
[]%
$ curl -X POST localhost:8080/todo-lists -H "Content-Type:application/json" --data "{ \"name\": \"My first TODO list\" }"
$ curl localhost:8080/todo-lists | jq
[
  {
    "id": "77D8FD15-D6C4-4EE1-9996-601370ED3329",
    "name": "My first TODO list"
  }
]

完成!我們設置好 Create 和 Read 操作了,餘下 Update 和 Delete 的實作,我就留給大家練習,來完成整個 CRUD 操作。

💡
提示:利用 app 定義一個 put(和一個 path),還有一個 delete 方法。

總結

來到這裡,希望這篇文章可以讓你了解 Vapor,並和我一樣喜歡這個框架。在之後的文章中,我們會繼續擴展這個待辦事項 App,並涵蓋以下內容:

  • Advanced routing
  • 參數 (parameter) 和 query 參數
  • Model 關係

謝謝你的閱讀。

本篇原文(標題:Getting Started With Vapor 4 —Learn By Building a Todo Application)刊登於作者 Medium,由 Fernando Moya de Rivas 所著,並授權翻譯及轉載。
作者簡介:Fernando Moya de Rivas,熱愛移動開發,對藝術和設計有濃厚的興趣。
譯者簡介:Kelly Chan-AppCoda 編輯小姐。
作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。