使用Vapor和Fluent创建REST API

斯威夫特很棒。 是的,它已经成熟(现在有了5.0,我们有了ABI稳定性,万岁!)。 您拥有OOP,POP,功能和命令式编程的强大功能。

如今,您几乎可以在Swift中做任何事情。 如果您想成为既了解后端又了解前端的全栈开发人员,那么本文适合您。
用Swift编写的最著名的Web框架是Kitura和Vapor。
Vapor现在是第3版(于2018年5月发布),是开源的,您可以轻松创建REST API,Web应用程序或很棒的网站。

在本教程中,您将学习:

  • 如何开始使用蒸气
  • 创建您的第一个REST API
  • 如何使用Fluent ORM Framework
  • 如何在Fluent中将1:M和M:M db关系转换为父子或兄弟姐妹关系
  • 将您所学的内容应用到真实的案例中

如果要跳过这一部分,可以在GitHub上找到整个项目:

radude89 / footballgather-ws
通过在GitHub上创建一个帐户为radude89 / footballgather-ws开发做出贡献。 github.com

先决条件

对于本教程,您将需要:

  • Xcode 10.2
  • 迅捷知识
  • REST API的基础知识
  • Swift Package Manager的一些知识

入门

首先,您需要从Mac App Store安装Xcode。
您可以使用brew安装Vapor Toolbox。 这很有用,因此我们可以为常见操作运行命令行任务。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 
 冲泡水龙头/自来水 
冲泡安装蒸气/抽头/蒸气

您准备好出发了!

足球聚会-iOS应用示例

FootballGather是一个演示项目,供朋友们聚在一起并尽可能快地参加足球比赛。
您可以通过查看以下样机(用Balsamiq创建)来想象客户端应用程序:

特征:

  • 坚持球员
  • 能够添加玩家
  • 设置比赛倒数计时器
  • 在离线模式下使用应用程序
  • 坚持球员

数据库结构

让我们使用下图所示的数据库模式:

通过这种方式,我们可以举例说明用户和收集之间的1:M关系,其中一个用户可以创建多个收集,而M:M播放器到集合,其中一个收集可以具有多个玩家,一个玩家可以在多个收集中进行游戏。

控制器清单

如果我们查看iOS应用程序,我们将创建以下控制器:

用户控制器

  • POST / api / users / login-用户的登录功能
  • POST / api / users-注册新用户
  • GET / api / users —获取用户列表
  • GET / api / users / {userId}-按ID获取用户
  • DELETE / api / users —获取用户列表

PlayerController

  • GET / api / players —获取玩家列表
  • GET / api / players / {playerId}-按ID获取玩家
  • GET / api / players / {playerId} / gathers-获取玩家的筹码列表
  • POST / api / players —添加新的播放器
  • DELETE / api / players / {playerId}-删除具有给定ID的玩家
  • PUT / api / players / {playerId}-通过其ID更新玩家

GatherController

  • GET / api / gathers-获取收集列表
  • GET / api / gathers / {gatherId}-按其ID获取收集
  • GET / api / gathers / {gatherId} / players —获取ID指定的集合中的球员列表
  • POST / api / gathers / {gatherId} / players / {playerId}-将玩家添加到聚会
  • POST / api / gathers-添加新集合
  • DELETE / api / gathers / {gatherId}-删除具有给定id的聚集
  • PUT / api / gathers / {gatherId}-通过其ID更新收集

应用程式结构

打开您在上一节中创建的Xcode项目。

类型:

 蒸气xcode -y 

可能还要等一下。

这是生成的文件:
├──公众
├──资料来源
│├──应用
││├──控制器
││├──型号
││├──boot.swift
││├──configure.swift
││└──route.swift
│└──跑
│└──main.swift
├──测试
│└──AppTests
└──Package.swift

您将在此项目中涉及的内容:

Package.swift
这是项目的清单,它定义了我们应用程序的所有依赖关系和目标。
我正在使用蒸气3.3.0。 您可以如下更改Package.swift:.package(URL:“ https://github.com/vapor/vapor.git”,来自:“ 3.3.0”)

上市
您想要公开的所有资源,例如图像。

资源
在这里,您可以看到两个单独的模块:App和Run。
通常,您必须将所有开发的代码放入“ App”中。 运行文件夹包含main.swift文件。

楷模
在这里添加您的Fluent模型。 在我们的应用中:用户,播放器,收集。

控制器
控制器是您编写REST API逻辑(例如CRUD操作)的地方。
与iOS ViewControllers类似,但是它们处理请求并管理模型。

route.swift
用于查找传入请求的适当响应。

configure.swift
在应用初始化之前调用。 注册路由器,中间件,数据库和模型迁移。

实现UserController

在开始实施我们的用户控制器之前,请删除生成的Todo相关代码:
控制器文件夹中的TodoController.swift。
来自Models的Todo.swift。
来自configure.swift的line migrations.add(model:Todo.self,数据库:.sqlite))
在routes中找到的所有内容都来自route.swift。

用户将由用户名和密码定义。 主键的类型为UUID,代表唯一的String。
创建一个新文件User.swift并将其添加到Models文件夹中。 在文件中添加一个名为User的类。

使它符合以下协议:

  • 可编码:将服务的参数映射到实际的类参数。
  • SQLiteUUIDModel:便利帮助程序协议,使模型成为
  • 以UUID作为主键的SQLite Model类。 用于引用属性的编译安全性。
  • 内容:用于通过蒸气轻松解码信息
  • 迁移:告诉Fluent如何配置数据库。
  • 参数:用于带有参数的请求,例如GET / users / {userId}。

现在您有了用户模型。 让我们创建UserController。

在Controllers文件夹中创建一个名为UserController的新结构。 使它符合RouteCollection协议。
暂时保留启动功能。

接下来,我们将为用户添加CRUD操作。

获取所有用户

  func getHandler(_ req:Request)抛出-> Future  { 
返回尝试req.parameters.next(User.self)
}

这将从请求中提取用户ID,并查询数据库以返回用户。

创建用户

  func createHandler(_ req:Request,user:User)抛出-> Future  { 
返回user.save(on:req).map {user in
var httpResponse = HTTPResponse()
httpResponse.status = .created
 如果让userId = user.id?.description { 
让location = req.http.url.path +“ /” + userId
httpResponse.headers.replaceOrAdd(名称:“位置”,值:位置)
}
  let response = Response(http:httpResponse,使用:req) 
返回响应
}
}

我们将使用保存功能返回用户对象。 遵循REST API标准,我们提取用户创建的UUID并作为响应的Location标头的一部分返回。

删除用户

  func deleteHandler(_ req:Request)抛出-> Future  { 
返回尝试req.parameters.next(User.self).flatMap(to:HTTPStatus.self){
返回user.delete(on:req).transform(to:.noContent)
}
}

首先,我们从请求参数中提取用户ID。 我们对用户执行删除功能,并返回不包含任何内容的HTTPStatus。

实现PlayerController

PlayerController遵循与UserController相同的模式。 在这种情况下,附加功能由更新部分组成。

  func updateHandler(_ req:Request)抛出-> Future  { 
return try flatMap(to:HTTPStatus.self,req.parameters.next(Player.self),req.content.decode(Player.self)){播放器,updatedPlayer在
player.age = UpdatedPlayer.age
player.name = UpdatedPlayer.name
player.preferredPosition = UpdatedPlayer.preferredPosition
player.favouriteTeam = UpdatedPlayer.favouriteTeam
player.skill = UpdatedPlayer.skill
 返回player.save(on:req).transform(to:.noContent) 
}
}

如果我们看一下此功能,我们首先要提取要执行更新的播放器的播放器ID。
接下来,我们提取所有属性并将它们映射到Player对象。 我们执行更新,您可能猜到了,我们将其称为save方法。

注册功能

  func boot(router:Router)抛出{ 
让playerRoute = router.grouped(“ api”,“ players”)
playerRoute.get(使用:getAllHandler)
playerRoute.get(Player.parameter,使用:getHandler)
playerRoute.post(Player.self,使用:createHandler)
playerRoute.delete(Player.parameter,使用:deleteHandler)
playerRoute.put(Player.parameter,使用:updateHandler)
playerRoute.get(Player.parameter,“ gathers”,使用:getGathersHandler)
}

对于GatherController,我们坚持使用与UserController相同的模式。

1:M和M:M关系

为了实现两个模型类之间的关系,我们将必须创建一个Pivot类。

 最终课程PlayerGatherPivot:SQLiteUUIDPivot { 
var ID:UUID?
var playerId:Player.ID
var collectId:Gather.ID
var小组:字串
  typealias Left =玩家 
typealias右=聚集
 静态变量leftIDKey:LeftIDKey = \ PlayerGatherPivot.playerId 
静态变量rightIDKey:RightIDKey = \ PlayerGatherPivot.gatherId
  init(playerId:Player.ID,collectId:Gather.ID,team:String){ 
self.playerId = playerId
self.gatherId = collectId
self.team =团队
}
  } 
  // Player.swift 
扩展播放器{
var收集:兄弟姐妹 {
返回兄弟姐妹()
}
}
  // Gather.swift 
扩展集合{
var player:兄弟姐妹 {
返回兄弟姐妹()
}
}

上面的实现描述了玩家和聚会之间的M:M关系。 我们使用左键作为玩家表的主键,并使用右键作为聚会的主键。
这类似于由FK / PK组成的M:M关系的主键。 ‘team’属性描述了玩家在当前聚会中所属的团队。
我们将必须在模型类中指定同级。 这是使用Swift的泛型原理完成的。

对于1:M关系,我们可以看一下User v Gather:

 最后一堂课:可编码{ 
var userId:User.ID
...
}
扩展集合{
var user:Parent {
返回parent(\。userId)
}
}

在我们的控制器类中,方法如下所示:

 扩展GatherController { 
func getPlayersHandler(_ req:Request)抛出-> Future {
返回尝试req.parameters.next(Gather.self).flatMap(to:[Player] .self){
返回尝试collect.players.query(on:req).all()
}
}
}
 扩展PlayerController { 
func getGathersHandler(_ req:Request)抛出-> Future {
返回try req.parameters.next(Player.self).flatMap(to:[Gather] .self){
返回try player.gathers.query(on:req).all()
}
}
}

注册路由并配置数据库

打开routes.swift并添加以下内部路由功能:

 让userController = UserController() 
尝试router.register(collection:userController)
 让playerController = PlayerController() 
尝试router.register(collection:playerController)
 让collectController = GatherController() 
尝试router.register(collection:collectController)

这些行将注册您的所有控制器。

 在configure.swift中,将所有模型添加到MigrationsConfig中: 
migrations.add(模型:User.self,数据库:.sqlite)
migrations.add(模型:Player.self,数据库:.sqlite)
migrations.add(模型:Gather.self,数据库:.sqlite)
migrations.add(模型:PlayerGatherPivot.self,数据库:.sqlite)

而已。 构建并运行。