使用 Swift 创建最佳 GraphQL API。GraphZahl 是一个 Swift 框架,让您以最简单的方式编写服务器!它拥有神奇的、类似 Codable 的 API。
以下是您可能会喜欢它的主要原因
让我们直奔主题,编写我们的 Hello World!
// Create a GraphQLSchema
enum HelloWorld: GraphQLSchema {
// Describe the Query Type
class Query: QueryType {
func greeting(name: String) -> String {
return "Hello, \(name)"
}
}
}
// Run a query using .perform
let result = HelloWorld.perform(request: "...")
您甚至可以将其连接到 GraphiQL 并进行测试
左:数据查询 | 中:来自我们 API 返回的 json | 右:我们的服务器 API 参考
让我们分解一下!
每个 GraphQL API 都以根 Schema 开始,您在其中定义查询类型和突变类型
现在我们先关注查询类型。GraphZahl 现在将查看任何可以在 GraphQL 中提供的属性和函数,并将它们作为 API 中的字段提供。
在我们的示例中,这意味着它将找到函数 greeting
,并看到所有输入和输出都可作为 GraphQL 类型使用并注册它。超级简单 😍!
您可以通过 Swift Package Manager 安装 GraphZahl,方法是将以下行添加到您的 Package.swift
文件中
import PackageDescription
let package = Package(
[...]
dependencies: [
.package(url: "https://github.com/nerdsupremacist/GraphZahl.git", from: "0.1.0-alpha.")
// It is recommended to use GraphZahl alongside Vapor
.package(url: "https://github.com/nerdsupremacist/graphzahl-vapor-support.git", from: "0.1.0-alpha.")
]
)
GraphZahl 的大多数用户需要了解六个主要提供的协议
以及一些扩展,这些扩展使您能够充分利用 GraphZahl 以及其他常见的服务器端库(如 Vapor 和 Fluent)。
您可以通过简单地让任何类实现 GraphQLObject
来提供它。
瞧 😍 !!!! 您不必实现任何东西。GraphZahl 将为您完成所有魔术
都将通过 GraphQL 提供。零麻烦。太棒了!!!
现在也可用了!就像那样。太棒了!!!
注意: 当涉及到 GraphQL 类型时,得益于条件一致性,GraphZahl 还开箱即用地支持 Optionals、Arrays 和 Futures。
让我们试试看
我们定义我们的类。带有一些属性和方法
class MyObject: GraphQLObject {
let greeting = "Hello World!"
let favouriteNumber = 42
func count(to number: Int) -> String {
return (0..<number).map(String.init).joined(separator: ", ")
}
}
我们可以看到它出现在我们的 API 中
左:MyObject 的数据查询 | 中:来自我们 API 返回的 json | 右:MyObject 的类型定义
您的对象也可以返回嵌套对象
class OtherObject: GraphQLObject {
let number: Int
init(number: Int) {
self.number = number
}
}
class MyObject: GraphQLObject {
...
let other = OtherObject(number: 1337)
let others = [OtherObject(number: 0), OtherObject(number: 1)] // Arrays also work
}
您可以立即看到结果
Schema 基本上是您在其中定义两个对象的命名空间:查询类型和突变类型。查询和突变的行为类似于常规的 GraphQLObject
。上面提到的所有功能都将开箱即用。
QueryType 是强制性的,并且始终必须定义!如果您的 API 不需要突变,那么您就完成了。
如果您想使数据用户依赖怎么办?
这就是为什么查询和突变类型带有一个额外的约束(为了简单起见,这在之前的代码片段中被省略了)。它们有所谓的关联 ViewerContext
,并且需要使用该 ViewerContext 进行初始化。注意:查询和突变的 ViewerContext 必须匹配。
例如,一个 Todo 应用程序可能看起来像这样
enum TodoApp: GraphQLSchema {
typealias ViewerContext = LoggedInUser?
class Query: QueryType {
let user: LoggedInUser?
func myTodos() -> [Todo]? {
return user?.todosFromDB()
}
required init(viewerContext user: LoggedInUser?) {
self.user = user
}
}
class Mutation: MutationType {
let user: LoggedInUser?
func deleteTodo(id: UUID) -> Todo? {
return user?.todos.find(id: id).delete()
}
required init(viewerContext user: LoggedInUser?) {
self.user = user
}
}
}
如果您不需要 Viewer Context,只需将其设置为 Void
enum HelloWorld: GraphQLSchema {
typealias ViewerContext = Void
class Query: QueryType {
func greeting(name: String) -> String {
return "Hello, \(name)"
}
required init(viewerContext: Void) { }
}
}
如果您有一个可以表示为标准标量值的值,您也可以返回该值,并具有额外的类型安全优势,即不会将其与标准类型混合。
要实现 GraphQLScalar
,您需要能够将其编码和解码为 ScalarValue
(字符串、数字、布尔值)
例如,如果您想返回 URL,您可以在扩展中实现它
extension URL: GraphQLScalar {
public init(scalar: ScalarValue) throws {
// attempt to read a string and read a url from it
guard let url = URL(string: try scalar.string()) else {
throw ...
}
self = url
}
public func encodeScalar() throws -> ScalarValue {
// delegate encoding to absolute string
return try absoluteString.encodeScalar()
}
}
搞定!每次对象中出现 URL 时,它都将可用
enum HelloWorld: GraphQLSchema {
class Query: QueryType {
let url = URL(string: "https://github.com/nerdsupremacist/GraphZahl")
}
}
您几乎可以使用所有类型的类型来做到这一点:您选择的格式的日期、百分比、HTML 文本,任何您想要的。
最后一个是最简单的情况。如果您想在 API 中支持枚举,它必须是 String 的 RawRepresentable 并实现 GraphQLEnum
。
如果您的枚举是 CaseIterable
,那就完成了!
enum Envirornment: String, CaseIterable, GraphQLEnum {
case production
case development
case testing
}
如果您想自己计算枚举 Cases,您可以实现 cases
函数。
GraphZahl 支持联合类型。要实现联合类型,您只需实现一个枚举,其中每个 case 都有一个关联的类型,该类型是一个对象
enum SearchResult: GraphQLUnion {
case user(User)
case page(Page)
case group(Group)
}
class Query: QueryType {
func search(term: String) -> [SearchResult] {
return [
.user(user),
.page(page),
.group(group),
]
}
}
如果您想将特定的结构体作为函数的参数,您可以使它们符合 GraphQLInputObject
enum Order: String, CaseIterable, GraphQLEnum {
case ascending
case descending
}
struct Options: GraphQLInputObject {
let safeSearch: Bool
let order: Order
}
class Query: QueryType {
func search(term: String,
arguments: Options = Options(safeSearch: true, order: .ascending)) -> [SearchResult] {
return [...]
}
}
GraphZahl 支持子类化,但由于 GraphQL 中不提供子类化,因此它被抽象为一个额外的接口。
例如
class A: GraphQLObject {
...
}
class B: A {
...
}
将被表示为接口 A
和具体类型 __A
和 B
// Interface that displays the output of any A
interface A {
...
}
// An instance of the superclass A
type __A implements A {
...
}
// An instance of the subclass B
type B implements A {
...
}
如果您将 KeyPath 作为函数的参数,GraphZahl 将创建一个枚举,映射到所有具有相同类型的属性。
例如
class SearchResult: GraphQLObject {
let relevance: Int
let popularity: Int
let name: String
}
class Schema: GraphQLSchema {
class Query: QueryType {
func search(term: String,
sortBy: KeyPath<SearchResult, Int>) -> [SearchResult] {
return [SearchResult]().sort { $0[keyPath: sortBy] < $1[keyPath: sortBy] }
}
}
}
输出的定义是
type Query {
search(sortBy: SearchResultField!, term: String!): [SearchResult!]!
}
type SearchResult {
name: String!
popularity: Int!
relevance: Int!
}
enum SearchResultField {
Relevance
Popularity
}
在 GraphZahl 之上还有一些扩展,用于添加对不同场景的支持,这些场景不一定是常态
要通过 Vapor 提供您的 API,您可以使用 graphzahl-vapor-support
enum HelloWorld: GraphQLSchema {
class Query: QueryType {
func greeting(name: String) -> String {
return "Hello, \(name)"
}
}
}
// Add the API to the Routes of your Vapor App
app.routes.graphql(path: "api", "graphql", use: HelloWorld.self)
您甚至可以添加 GraphiQL
app.routes.graphql(path: "api", "graphql", use: HelloWorld.self, includeGraphiQL: true)
要在您的 API 中使用 Fluent 类型和模型,您可以使用 graphzahl-fluent-support
enum API: GraphQLSchema {
typealias ViewerContext = Database
class Query: QueryType {
let database: Database
// QueryBuilders are supported with additional paging API
func todos() -> QueryBuilder<Todo> {
return Todo.query(on: database)
}
required init(viewerContext database: Database) {
self.database = database
}
}
...
}
它增加了对以下内容的支持
如果您要部署到 Heroku,这非常简单。您需要 2 样东西
1. 添加构建包
将构建包添加到 heroku
heroku buildpacks:set nerdsupremacist/graph-zahl
2. 添加 Procfile
在我们的 Repo 中,我们将添加一个 Procfile,它将告诉 Heroku 我们应用程序的起点
例如,当我们的 API 的 Target 被称为 MyServer
并且正在使用 Vapor 时
web: MyServer serve --env production --hostname 0.0.0.0 --port $PORT
您还可以从 Vapor 的部署文档中获得一些灵感。
如果您要为 Linux 构建 GraphZahl 应用程序,您需要添加 -E
链接器标志。例如
swift build -Xlinker -E {OTHER_FLAGS} -c debug
欢迎并鼓励贡献!
GraphZahl 与客户端的 Graphaello 结合使用效果最佳。Graphaello 使您能够直接从 SwiftUI 视图中使用 GraphQL。
GraphZahl 以芝麻街的 Count von Count 命名,但在德语中是 “Graf Zahl”。
GraphZahl 在底层使用了 GraphQLSwift、Runtime 和 Swift NIO。如果您正在寻找替代方案,请查看 Graphiti,它使用起来更冗长和复杂,但为您提供更多控制和更好的性能。
目前这是一个研究项目。有关其工作原理的更多详细信息将在稍后发布。构建这个非常困难,所以相信我,我真的很想详细讨论它... 😉
GraphZahl 在 MIT 许可证下可用。有关更多信息,请参见 LICENSE 文件。
本项目是在慕尼黑工业大学应用软件工程系的主管下完成的。该系拥有使用和维护此工具的永久权利。