Skip to content

Add Future support and example app #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: update-to-vapor-3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
/.build
/Packages
Package.resolved
/*.xcodeproj
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ os:
language: generic
sudo: required
dist: trusty
osx_image: xcode9.3
osx_image: xcode10
env:
- SWIFT_VERSION=4.1
- SWIFT_VERSION=4.2
install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew bundle; fi
- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
Expand Down
187 changes: 187 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 17 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,30 @@ let package = Package(
.library(
name: "VaporGraphQL",
targets: ["VaporGraphQL"]),
.library(
name: "StarWars",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved these to a separate framework so they can be referenced from both the tests and the example app.

targets: ["StarWars"]),
.executable(
name: "Example",
targets: ["Example"])
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/GraphQLSwift/GraphQL.git", .upToNextMajor(from: "0.5.0"))
.package(url: "https://github.com/vapor/vapor.git", .exact("3.1.0")),
.package(url: "https://github.com/noahemmet/GraphQL.git", .branch("spm")),
.package(url: "https://github.com/noahemmet/Graphiti.git", .branch("master")),
],
targets: [
.target(
name: "VaporGraphQL",
dependencies: ["Vapor", "GraphQL"]),
dependencies: ["Vapor", "GraphQL", "Graphiti"]),
.target(
name: "StarWars",
dependencies: ["Vapor", "GraphQL", "Graphiti"]),
.target(
name: "Example",
dependencies: ["VaporGraphQL", "StarWars", "Vapor", "GraphQL", "Graphiti"]),
.testTarget(
name: "VaporGraphQLTests",
dependencies: ["VaporGraphQL"]),
dependencies: ["VaporGraphQL", "StarWars"]),
]
)
60 changes: 8 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import PackageDescription
let package = Package(
dependencies: [
...
.package(url: "https://github.com/stevenlambion/vapor-graphql.git", .upToNextMajor(from: "0.1.0")),
.package(url: "https://github.com/stevenlambion/GraphQLRouteCollection.git", .upToNextMajor(from: "0.1.0")),
],
.target(
name: "App",
Expand All @@ -36,21 +36,17 @@ services.register(HTTPGraphQL() { req in (
schema: schema,
rootValue: [:],
context: req
)});
)})
```

Then use it in your app's routing:
Then route it in your app's `config.swift` file:

```swift
let router = app.make(Router.self)
let graphql = app.make(GraphQLService.self)

router.get("/graphql") { req in
return graphql.execute(req)
}
router.post("/graphql") { req in
return graphql.execute(req)
}
let router = EngineRouter.default()
let graphQLRouteCollection = GraphQLRouteCollection(enableGraphiQL: true)
try graphQLRouteCollection.boot(router: router)
try routes(router)
services.register(router, as: Router.self)
```

### Introspection
Expand Down Expand Up @@ -100,46 +96,6 @@ You can also enable it for the route collection:
GraphQLRouteCollection(enableGraphiQL: true)
```

### Type Safety

The Swift GraphQL library does not provide a fully type safe environment currently. To mitigate this, there's a `withTypedResolve()` decorator function to wrap resolvers that provide typed sources and context objects. This function has multiple overloads for both field and type resolvers. The function helps to provide better type support in development and runtime checking of the GraphQL API.

```swift
let UserType = try! GraphQLObjectType(
name: "User",
fields: [
"id": GraphQLField(type: GraphQLNonNull(GraphQLID)),
"email": GraphQLField(type: GraphQLNonNull(GraphQLString)),
"role": GraphQLField(
type: UserRoleType,
resolve: withTypedResolve { (user: User, _,) -> Role in
try user.role.get().wait()
})
]
)
```

### Async Support

Vapor 3 has a strong focus on async, however, the Swift GraphQL library hasn't implemented this functionality yet. To mitigate this problem the HTTPGraphQL service runs executions using GCD so as not to block the NIO event loop. It also uses the concurrency strategy for queries and subscriptions for parallel field resolving.

For async resolvers, a `resolveWithFuture()` decorator is provided. This allows resolvers to work with async code based on Swift NIO as they typically would in Vapor. `resolveWithFuture()` works by waiting for the results under an async queue separate from the NIO event loop. `resolveWithFuture()` is composed using the `withTypedResolve()` decorator, so it also includes type handling.

```swift
let UserType = try! GraphQLObjectType(
name: "User",
fields: [
"id": GraphQLField(type: GraphQLNonNull(GraphQLID)),
"email": GraphQLField(type: GraphQLNonNull(GraphQLString)),
"role": GraphQLField(
type: UserRoleType,
resolve: resolveWithFuture { (user: User, _,) -> Future<Role> in
try user.role.get()
})
]
)
```

## License

This project is released under the MIT license. See [LICENSE](LICENSE) for details.
Expand Down
11 changes: 11 additions & 0 deletions Sources/Example/app.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Vapor

/// Creates an instance of Application. This is called from main.swift in the run target.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are just Vapor's default new project files.

public func app(_ env: Environment) throws -> Application {
var config = Config.default()
var env = env
var services = Services.default()
try configure(&config, &env, &services)
let app = try Application(config: config, environment: env, services: services)
return app
}
26 changes: 26 additions & 0 deletions Sources/Example/configure.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Vapor
import VaporGraphQL
import StarWars

/// Called before your application initializes.
public func configure(_ config: inout Config, _ env: inout Vapor.Environment, _ services: inout Services) throws {
let httpGraphQL = HTTPGraphQL() { req in
return ExecutionContext(
schema: starWarsSchema,
eventLoopGroup: req
)
}
services.register(httpGraphQL, as: GraphQLService.self)

/// Register routes to the router
let router = EngineRouter.default()
let graphQLRouteCollection = GraphQLRouteCollection(enableGraphiQL: true)
try graphQLRouteCollection.boot(router: router)
try routes(router)
services.register(router, as: Router.self)

/// Register middleware
var middlewares = MiddlewareConfig() // Create _empty_ middleware config
middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response
services.register(middlewares)
}
6 changes: 6 additions & 0 deletions Sources/Example/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Vapor
import VaporGraphQL
import GraphQL

public let application = try app(.detect())
try application.run()
7 changes: 7 additions & 0 deletions Sources/Example/routes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Vapor

/// Register your application's routes here.
public func routes(_ router: Router) throws {
// GraphQL and GraphiQL are handled by the RouteCollection in `configure.swift`,
// but additional endpoints can still be added here.
}
Loading