• Stars
    star
    143
  • Rank 257,007 (Top 6 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 4 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Framework to implement Declarative, Type-Safe GraphQL Server APIs using Runtime Magic 🎩

low effort logo

GraphZahl (Alpha)

Swift Documentation

Create the best GraphQL APIs ever using Swift. GraphZahl is a Swift Framework that let's you write your Server the simplest way possible! With a magic, Codable-like API.

Here are the main reasons, why you could like it:

  • 📝 Declarative
  • 📦 Compositable and extendable
  • 🔐 Type-Safety
  • 🕓 ZERO Boilerplate
  • 😍 Familiar and Intuitive Codable-like Feel
  • 🤪 Amazing for Quick Prototyping

TL;DR?

Let's cut the chase and write our 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: "...")

And you can even connect it to GraphiQL and test it:

graphiql left: a query for data | middle: the returned json from our API | right: our server API reference

Let's break that down!

Every GraphQL API begins with a root Schema where you define the Query Type and Mutation Type:

  • Query Type (Mandatory): Describes all the read operations that can be done
  • Mutation Type (Optional): Descripes all the write operations

For now we'll focus on the Query Type. GraphZahl will now look at any properties and functions that can be served in GraphQL, and will offer those as fields in your API.

In our example this means that it will find the function greeting and see that all the inputs and outputs are availanle as GraphQL Types and register it. Super easy 😍!

Installation

Swift Package Manager

You can install GraphZahl via Swift Package Manager by adding the following line to your 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.")
    ]
)

Usage

Most users of GraphZahl need to understand the six main provided protocols:

As well as the extensions that enable you to get the most of GraphZahl alongside other common server-side libraries like Vapor and Fluent.

GraphQLObject - Reference

You can provide any class you want by simply making it implement GraphQLObject.

And voila 😍 !!!! You don't have to implement anything. GraphZahl will do all the magic for you:

  • Every property that is either: a. a GraphQL Object, a. Scalar a. Enum a. or Union

will be available via GraphQL. Zero hassle. Crazy!!!

  • Every method where: a. every input is a Scalar, Enum or Input Object a. the return type is either: a. a GraphQL Object, a. Scalar a. Enum a. or Union

is now also available! Just like that. Awesome!!!

Note: when it comes to GraphQL Types GraphZahl also supports Optionals, Arrays and Futures out of the box, thanks to Conditional Conformance.

Let's try it:

We define our class. With some properties and methods:

class MyObject: GraphQLObject {
    let greeting = "Hello World!"
    let favouriteNumber = 42

    func count(to number: Int) -> String {
        return (0..<number).map(String.init).joined(separator: ", ")
    }
}

And we can see it appear in our API:

left: a query for the data of MyObject | middle: the returned json from our API | right: the type definition of MyObject

Your object can also return nested objects:

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
}

And you can see the results immediately:

GraphQLSchema - Reference

A Schema is basically the namespace where you define two objects: A Query and a Mutation Type. The query and mutation behave like regular GraphQLObjects. All the features mentioned above will be included out of the box.

The QueryType is mandatory and always has to be defined! If your API doesn't need Mutations, then you're done.

What if you want to make data user dependent?

That's why Query and Mutation Types come with an extra constraint (for simplicity this was omitted in the snippets before). They have what we call an associated ViewerContext and need an initializer with that ViewerContext. Note: the ViewerContext of the Query and the Mutation have to Match.

For example a Todo App might look like this:

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
        }
    }
}

If you don't need a Viewer Context just set it to Void:

enum HelloWorld: GraphQLSchema {
    typealias ViewerContext = Void

    class Query: QueryType {
        func greeting(name: String) -> String {
            return "Hello, \(name)"
        }

        required init(viewerContext: Void) { }
    }
}

GraphQLScalar - Reference

If you have a value that can be represented as a standard Scalar Value, you can return that value as well, with the added Type Safety benefit, of not mixing it with the standard types.

To implement a GraphQLScalar you need to be able to encode and decode it as a ScalarValue (String, Number, Bool)

For example if you want to return URLs you can implement it in an extension:

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()
    }
}

And presto! Every time a URL comes up in an Object, it will be made available:

enum HelloWorld: GraphQLSchema {
    class Query: QueryType {
        let url = URL(string: "https://github.com/nerdsupremacist/GraphZahl")
    }
}

You can do this with virtually all kinds of types: Dates in the format of your choice, Percentages, HTML Text, whatever you want.

GraphQLEnum - Reference

The last one is the simplest case. If you want to support an enum in your API, it has to be RawRepresentable with String and implement GraphQLEnum.

If your enum is CaseIterable that's it!

enum Envirornment: String, CaseIterable, GraphQLEnum {
    case production
    case development
    case testing
}

If you want to compute your Enum Cases yourself you can implement the cases function.

GraphQLUnion - Reference

GraphZahl supports Union Types. To implement a Union Type you just have to implement an enum where every case has an associated type that is an Object

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 - Reference

If you want to take specific structs as arguments for functions you can make them conform to 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 [...]
    }
}

Subclassing Support

GraphZahl supports subclassing, but due to Subclassing not being available in GraphQL, it is abstracted as an extra Interface.

For example:

class A: GraphQLObject {
    ...
}

class B: A {
    ...
}

will be represented as the interface A and the concrete types __A and 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 Support

If you take KeyPaths as an argument of a function, GraphZahl will create an enum mapping to all the properties with the same type.

For example:

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] }
        }
    }
}

And the outputed definition is:

type Query {
  search(sortBy: SearchResultField!, term: String!): [SearchResult!]!
}

type SearchResult {
  name: String!
  popularity: Int!
  relevance: Int!
}

enum SearchResultField {
  Relevance
  Popularity
}

Extensions and Plugins

There's also some extensions on top of GraphZahl to add support for different scenarios that are not necessarily the norm:

Vapor Support (Recommended)

To serve your API via Vapor, you can use 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)

And you can even add GraphiQL:

app.routes.graphql(path: "api", "graphql", use: HelloWorld.self, includeGraphiQL: true)

Fluent Support

To use Fluent Types and Models in your API, you can use 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
        }
    }
    
    ...
}

It adds support for:

  • QueryBuilder
  • @Parent
  • @Children
  • @Siblings
  • @Field
  • @ID

Deploy

Heroku

If you're deploying to Heroku, it's super simple. You'll need 2 things:

1. Add the build pack

Add the build pack to heroku:

heroku buildpacks:set nerdsupremacist/graph-zahl

2. Add a Procfile

In our Repo we will add a Procfile that will tell Heroku the starting point of our app:

For example, where the Target of our API is called MyServer and is using Vapor:

web: MyServer serve --env production --hostname 0.0.0.0 --port $PORT

You can also take some inspiration from the Deployment documentation for Vapor.

Building for Linux

If you're building a GraphZahl app for Linux, you'll need to add -E linker flag. For example:

swift build -Xlinker -E {OTHER_FLAGS} -c debug

Known Issues

  • Due to issues with combinatorics the amount of arguments for a method has been limited
  • Calling methods hasn't been tested against every argument and return type combination possible. If you encounter any crashes or failures, please open an issue

Contributions

Contributions are welcome and encouraged!

Related Work

GraphZahl works best when coupled with Graphaello on the Client Side. Graphaello enables you to use GraphQL directly from your SwiftUI Views.

Learn

GraphZahl is named after Count von Count from Sesame Street but in German "Graf Zahl".

GraphZahl uses GraphQLSwift, Runtime and Swift NIO under the Hood. If you are looking for an alternative check out Graphiti, which is more verbose and complex to use, but offers you more control and better performance.

This is currenlty a research project. More details about how it works, will be published later. This was very difficult to build, so trust me, I really want to talk in detail about it... ;)

License

GraphZahl is available under the MIT license. See the LICENSE file for more info.

This project is being done under the supervision of the Chair for Applied Software Enginnering at the Technical University of Munich. The chair has everlasting rights to use and maintain this tool.

More Repositories

1

FancyScrollView

A SwiftUI ScrollView Designed to imitate the App Store and Apple Music ScrollViews (with or without a Parallax Header)
Swift
971
star
2

Snap

A customizable Snapping Drawer à la Apple Maps.

 100% in SwiftUI.
Swift
717
star
3

Graphaello

A Tool for Writing Declarative, Type-Safe and Data-Driven Applications in SwiftUI using GraphQL
Swift
493
star
4

Sync

Real-time Apps the SwiftUI way
Swift
157
star
5

Syntax

Write value-driven parsers quickly in Swift with an intuitive SwiftUI-like DSL
Swift
146
star
6

CovidUI

A simple App to Track the status of Covid-19 around the World. Using SwiftUI and GraphQL
Swift
78
star
7

memes

An online Multi-Player Meme Party Game written in Swift
Swift
70
star
8

Valid

Input Validation Done Right. A Swift DSL for Validating User Input using Allow/Deny Rules
Swift
37
star
9

CovidQL

GraphQL API for querying data related to Covid-19
Swift
31
star
10

StopTouchingYourFace

SwiftUI App that alerts you when you have touched your face
Swift
25
star
11

tmdb

A GraphQL Wrapper for The Movie Database
Swift
24
star
12

Sweeft

Swift but a bit Sweeter - More Syntactic Sugar for Swift #MakeSwiftGreatAgain
Swift
21
star
13

AssociatedTypeRequirementsKit

A Swift µFramework for dealing with the classic "Self or associated type requirements" errors
Swift
20
star
14

Fuzzi

Locally searching in Swift made simple (and fuzzily)
Swift
19
star
15

git-yolo

YOLO Mode for GIT
JavaScript
16
star
16

VariadicViewBuilder

Custom View Builder that allows you to build custom layouts
Swift
15
star
17

trumpify

Make your words be the BEST WORDS - Instant text trumpifier
JavaScript
14
star
18

VideoPlayer

µFramework containing a SwiftUI Video Player allowing for custom controls
Swift
13
star
19

Protected

Experimental API for Reads and Writes protected via Phantom types
Swift
11
star
20

LlamaLang

Repository for the Llama Programming Language. Work In Progress
Python
11
star
21

syntax-highlight-publish-plugin

Plugin to add syntax highlighting (for multiple languages) to your Publish Site, with the least amount of effort.
Swift
7
star
22

graphzahl-todo-app-example

Example Todo App Showcasing how to use GraphZahl with Vapor and Fluent
Swift
7
star
23

SyncExampleApp

Example App on how to use Sync to keep objects in sync
Swift
7
star
24

tmdb-relay-test

Test Project showcasing Relay + Typescript + GitHub Actions + Chakra
TypeScript
6
star
25

SyncTokamak

Swift
6
star
26

graphzahl-vapor-support

A set of extensions that allow to use GraphZahl with Vapor
Swift
6
star
27

GridView

A SwiftUI View that allows you to Render items in a Grid
Swift
5
star
28

Mealy

Swift Framework for Model-Based Testing using Mealy Machines
Swift
5
star
29

stalky-app

Always remember people's names
Swift
5
star
30

CodableEnum

µFramework that allows you to make an Enum with associated values conform to Codable
Swift
5
star
31

SyncWebSocketVapor

Web Socket Server Support for Sync
Swift
4
star
32

Pushie

Push Down Automata Creation in Swift. The easy way.
Swift
4
star
33

git-fuck

NPM Module for when you've committed to the wrong branch
JavaScript
4
star
34

graphaello-music-example

Example App using Graphaello
Swift
4
star
35

Assert

Swift UI Like DSL for Building Tests
Swift
4
star
36

MarkdownSyntax

Markdown Parser using Syntax
Swift
4
star
37

graphzahl-fluent-support

A set of extensions that allow to use Fluent APIs with GraphZahl
Swift
4
star
38

fresh-mac-setup

How to set up a fresh Mac the way I like it
Ruby
3
star
39

SyntaxTree

Model for building Syntax Trees
Swift
3
star
40

graphql-syntax

GraphQL Parser written in Syntax
Swift
3
star
41

graphaello-countries-example

Example App using Graphaello
Swift
3
star
42

GraphDSL

Script that given a GraphQL Server will create a Kotlin DSL for writing queries
Kotlin
2
star
43

graphzahl-vs-graphiti

Comparison of the same schema implemented with GraphZahl and with Graphiti as a comparison
Swift
1
star
44

SyncWebSocketWebAssemblyClient

Swift
1
star
45

ContextKit

A Public Basic API for providing a Compositable Type Safe Dict
Swift
1
star
46

SyncWebSocketClient

Web Socket Client support for Sync
Swift
1
star
47

syntax-highlight

Syntax Highlighting Extensions to the Syntax Framework
Swift
1
star
48

kotlin-worldbank-type-provider

Type Provider for the WorldBank API inside of Kotlin Scripting
Kotlin
1
star
49

kotlin-type-provider-template

Template for building Type Providers for Kotlin Scripting
Kotlin
1
star
50

TextMate

Support for parsing Text Mate Languages
Swift
1
star
51

StarWarsArray

A Swift Sequence indexed like Star Wars Movies
Swift
1
star
52

isAdnanGonnaBeLate

A simple Web Page with the Status of whether or not Adnan Will be Late
HTML
1
star