带有蒸气2的服务器端Swift(第1部分)
在2015年Swift开源之前,我曾尝试用Node.js编写一个简单的RESTful API服务器。 但是,由于我对Javascript语法和后端开发不熟悉,所以没有足够的时间来深入研究。 如今,有几种不同的服务器端Swift框架,例如Vapor,Perfect和Kitura。 作为iOS开发人员,这是拓展后端开发视野的好机会。
根据其GitHub页面,Vapor是Swift最常用的Web框架。 它为您的下一个网站,API或云项目提供了精美而易用的基础。 更重要的是,raywenderlich.com网站上有一系列免费教程。 这些教程为我提供了如何使用Vapor构建API服务器的基本概念,即使它们是在先前版本中完成的。 谢谢雷!
在本文中,我将演示一个简单的RESTful API服务器,该服务器将处理三种模型: Lesson
, Teacher
和Student
。 基本上,我们需要以下端点:
-
/{models}
:通过GET请求获取所有模型对象,或者通过带有JSON正文的POST请求创建新的模型对象。 (只需将{models}替换为课程,教师和学生。) -
/{models}/id
:通过GET请求获取特定的模型对象,通过DELETE请求删除一个模型对象,或通过带有JSON正文的PATCH请求更新现有模型对象。
在我们开始编码之前,有几种必要的配置。 首先,按照Vapor网站上的说明正确安装Vapor,然后使用vapor new your_project_name --template=api
创建一个新项目。 其次,在该项目中,我选择使用PostgreSQL作为数据库服务器,因此有必要在Macbook上安装PostgreSQL,并将蒸气的PostgreSQL提供程序添加到我的项目依赖项中。 启动PostgreSQL服务器后,按如下所示打开并修改Package.swift
文件。
let package = Package( // ... dependencies: [ .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), .Package(url: "https://github.com/vapor-community/postgresql-provider.git", majorVersion: 2) ], // ... )
然后,在目录Config/secrets
下创建一个名为postgresql.json
的新文件,并将以下代码片段添加到postgresql.json
文件中。
{ "hostname": your_host_address, "user": your_username, "password": your_password, "database": your_database_name, "port": 5432 }
请记住用以下命令替换Config/fluent.json
的驱动程序。
"driver": "postgresql"
最后一步是在Config+Setup.swift
文件中添加PostgreSQL提供程序。
import PostgreSQLProvider
extension Config { // ... private func setupProviders() throws { try addProvider(FluentProvider.Provider.self) try addProvider(PostgreSQLProvider.Provider.self) } // ... }
extension Config { // ... private func setupProviders() throws { try addProvider(FluentProvider.Provider.self) try addProvider(PostgreSQLProvider.Provider.self) } // ... }
完成所有配置后,使用vapor build
和vapor xcode
获取必要的依赖关系并生成一个新的Xcode项目。
首先让我们处理模型类。 打开我们在上一步中生成的Xcode项目,然后将Lesson.swift
, Teacher.swift
和Student.swift
文件添加到Models
组中。 在将新文件添加到我们的项目时,请确保选择目标为App。
此外,请按照Vapor的文档正确定义我们的模型。 因为它们的实现非常相似,所以我只显示Lesson.swift
如下。
import PostgreSQLProvider
final class Lesson: Model { let storage = Storage() // This is for Storable protocol
final class Lesson: Model { let storage = Storage() // This is for Storable protocol
final class Lesson: Model { let storage = Storage() // This is for Storable protocol
var title: String
// Use these keys instead of magic strings static let idKey = "id" static let titleKey = "title"
// Use these keys instead of magic strings static let idKey = "id" static let titleKey = "title"
// Use these keys instead of magic strings static let idKey = "id" static let titleKey = "title"
init(title: String) { self.title = title }
init(title: String) { self.title = title }
init(title: String) { self.title = title }
init(row: Row) throws { self.title = try row.get(Lesson.titleKey) }
init(row: Row) throws { self.title = try row.get(Lesson.titleKey) }
init(row: Row) throws { self.title = try row.get(Lesson.titleKey) }
func makeRow() throws -> Row { var row = Row() try row.set(Lesson.titleKey, title) return row } }
func makeRow() throws -> Row { var row = Row() try row.set(Lesson.titleKey, title) return row } }
func makeRow() throws -> Row { var row = Row() try row.set(Lesson.titleKey, title) return row } }
// For database prepare and revert extension Lesson: Preparation { static func prepare(_ database: Database) throws { try database.create(self) { (user) in user.id() user.string(Lesson.titleKey) } }
// For database prepare and revert extension Lesson: Preparation { static func prepare(_ database: Database) throws { try database.create(self) { (user) in user.id() user.string(Lesson.titleKey) } }
// For database prepare and revert extension Lesson: Preparation { static func prepare(_ database: Database) throws { try database.create(self) { (user) in user.id() user.string(Lesson.titleKey) } }
static func revert(_ database: Database) throws { try database.delete(self) } }
static func revert(_ database: Database) throws { try database.delete(self) } }
static func revert(_ database: Database) throws { try database.delete(self) } }
// Convenience of generate model from JSON extension Lesson: JSONConvertible { convenience init(json: JSON) throws { self.init(title: try json.get(Lesson.titleKey)) }
// Convenience of generate model from JSON extension Lesson: JSONConvertible { convenience init(json: JSON) throws { self.init(title: try json.get(Lesson.titleKey)) }
// Convenience of generate model from JSON extension Lesson: JSONConvertible { convenience init(json: JSON) throws { self.init(title: try json.get(Lesson.titleKey)) }
func makeJSON() throws -> JSON { var json = JSON() try json.set(Lesson.idKey, id?.string) try json.set(Lesson.titleKey, title) return json } }
func makeJSON() throws -> JSON { var json = JSON() try json.set(Lesson.idKey, id?.string) try json.set(Lesson.titleKey, title) return json } }
func makeJSON() throws -> JSON { var json = JSON() try json.set(Lesson.idKey, id?.string) try json.set(Lesson.titleKey, title) return json } }
// Convenience of returning response extension Lesson: ResponseRepresentable {}
// Convenience of returning response extension Lesson: ResponseRepresentable {}
创建模型后,切换到Config+Setup.swift
文件并添加准备。
private func setupPreparations() throws { preparations.append(Lesson.self) preparations.append(Teacher.self) preparations.append(Student.self) }
接下来,也将与我们的模型相对应的控制器添加到Controllers
组中。 在每个控制器内部,我们可以利用Vapor的ResourceRepresentable
协议的优势来处理模型的CRUD。 再次,为简单起见,我仅显示以下内容的LessonController.swift
实现。
import PostgreSQLProvider
final class LessonController { fileprivate func getAll(request: Request) throws -> ResponseRepresentable { return try Lesson.all().makeJSON() }
final class LessonController { fileprivate func getAll(request: Request) throws -> ResponseRepresentable { return try Lesson.all().makeJSON() }
final class LessonController { fileprivate func getAll(request: Request) throws -> ResponseRepresentable { return try Lesson.all().makeJSON() }
fileprivate func getOne(request: Request, lesson: Lesson) throws -> ResponseRepresentable { return lesson }
fileprivate func getOne(request: Request, lesson: Lesson) throws -> ResponseRepresentable { return lesson }
fileprivate func getOne(request: Request, lesson: Lesson) throws -> ResponseRepresentable { return lesson }
fileprivate func create(request: Request) throws -> ResponseRepresentable { let lesson = try request.lesson() try lesson.save() return lesson }
fileprivate func create(request: Request) throws -> ResponseRepresentable { let lesson = try request.lesson() try lesson.save() return lesson }
fileprivate func create(request: Request) throws -> ResponseRepresentable { let lesson = try request.lesson() try lesson.save() return lesson }
fileprivate func update(request: Request, lesson: Lesson) throws -> ResponseRepresentable { let newLesson = try request.lesson() lesson.title = newLesson.title try lesson.save() return lesson }
fileprivate func update(request: Request, lesson: Lesson) throws -> ResponseRepresentable { let newLesson = try request.lesson() lesson.title = newLesson.title try lesson.save() return lesson }
fileprivate func update(request: Request, lesson: Lesson) throws -> ResponseRepresentable { let newLesson = try request.lesson() lesson.title = newLesson.title try lesson.save() return lesson }
fileprivate func delete(request: Request, lesson: Lesson) throws -> ResponseRepresentable { try lesson.delete() return lesson } }
fileprivate func delete(request: Request, lesson: Lesson) throws -> ResponseRepresentable { try lesson.delete() return lesson } }
fileprivate func delete(request: Request, lesson: Lesson) throws -> ResponseRepresentable { try lesson.delete() return lesson } }
// Notice the difference between Item and Muliple extension LessonController: ResourceRepresentable { func makeResource() -> Resource { return Resource( index: getAll, store: create, show: getOne, update: update, destroy: delete ) } }
// Notice the difference between Item and Muliple extension LessonController: ResourceRepresentable { func makeResource() -> Resource { return Resource( index: getAll, store: create, show: getOne, update: update, destroy: delete ) } }
// Notice the difference between Item and Muliple extension LessonController: ResourceRepresentable { func makeResource() -> Resource { return Resource( index: getAll, store: create, show: getOne, update: update, destroy: delete ) } }
// Convenience of retrieving Lesson object extension Request { fileprivate func lesson() throws -> Lesson { guard let json = json else { throw Abort.badRequest }
// Convenience of retrieving Lesson object extension Request { fileprivate func lesson() throws -> Lesson { guard let json = json else { throw Abort.badRequest }
// Convenience of retrieving Lesson object extension Request { fileprivate func lesson() throws -> Lesson { guard let json = json else { throw Abort.badRequest }
return try Lesson(json: json) } }
return try Lesson(json: json) } }
最后,通过Router.swift
添加到Router.swift
文件中,连接我们的控制器和Droplet
对象。
func setupRoutes() throws { let lessonController = LessonController() resource("lessons", lessonController)
func setupRoutes() throws { let lessonController = LessonController() resource("lessons", lessonController)
let teacherController = TeacherController() resource("teachers", teacherController)
let teacherController = TeacherController() resource("teachers", teacherController)
let teacherController = TeacherController() resource("teachers", teacherController)
let studentController = StudentController() resource("students", studentController) }
let teacherController = TeacherController() resource("teachers", teacherController)
let studentController = StudentController() resource("students", studentController) }
let studentController = StudentController() resource("students", studentController) }
至此,我们实现了一个简单的RESTful API服务器,并且可以使用Postman测试模型CRUD。 在本系列的第2部分中,我将演示如何在两个模型之间实现同级(多对多)关系。