#
Vapor
Pioneer will have a built-in first-party integration with Vapor. This aims to make developing with Pioneer faster by not having to worry about creating integrations for the most common option for a web framework in Swift.
This integration added a couple additional benefits.
import Pioneer
import Vapor
let app = try Application(.detect())
let server = Pioneer(
schema: schema,
resolver: Resolver(),
websocketProtocol: .graphqlWs,
introspection: true,
playground: .sandbox
)
app.middleware.use(
server.vaporMiddleware()
)
#
Context
#
HTTP-based Context
Pioneer provide a similar solution to @apollo/server/express4
for building context using the raw HTTP requests and responses. It provide both in the context builder that needed to be provided when create the middleware.
This request and response will be request-specific / different for each GraphQL HTTP request.
import Pioneer
import Vapor
let app = try Application(.detect())
let server = Pioneer(
schema: schema,
resolver: Resolver(),
websocketProtocol: .graphqlWs,
introspection: true,
playground: .sandbox
)
app.middleware.use(
server.vaporMiddleware(
context: { (req: Request, res: Response) in
...
}
)
)
#
Request (HTTP)
The request given is directly from Vapor, so you can use any method you would use in a regular Vapor application to get any values from it.
func someCookie(ctx: Context, _: NoArguments) async -> String {
return ctx.req.cookies["some-key"]
}
#
Response
The response object is already provided in the context builder that is going to be the one used to respond to the request.
You don't need return one, and instead just mutate its properties.
func users(ctx: Context, _: NoArguments) async -> [User] {
ctx.response.cookies["refresh-token"] = /* refresh token */
ctx.response.cookies["access-token"] = /* access token */
return await getUsers()
}
#
Websocket-based Context
Vapor integration also allow seperate context builder which is similar to what you can provide to the context
property in graphql-ws where you are given the
WebSocket context builder is optional.
Pioneer's Vapor integration will try to use the HTTP context builder for WebSocket by providing all the relevant information into the
import Pioneer
import Vapor
let app = try Application(.detect())
let server = Pioneer(
schema: schema,
resolver: Resolver(),
websocketProtocol: .graphqlWs,
introspection: true,
playground: .sandbox
)
app.middleware.use(
server.vaporMiddleware(
context: { (req: Request, res: Response) in
...
},
websocketContext: { (req: Request, payload: Payload, gql: GraphQLRequest) in
...
}
)
)
#
Request (WS)
The request given is directly from Vapor when upgrading to websocket, so you can use any method you would use in a regular Vapor application to get any values from it.
Switching Protocol Request
This request object will be the same for each websocket connection and will not change unless the new connection is made.
It will also not have any custom headers and the operation specific graphql query which is different from request given in HTTP.
struct Resolver {
func something(ctx: Context, _: NoArguments) async -> [User] {
return User.query(on: ctx.req.db).all()
}
}
This is only for using 1 shared context builder, and not providing a separate WebSocket context builder.
The custom request will similar to the request used to upgrade to websocket but will have:
- The headers taken from
"header"/"headers"
value from thePayload
or all the entirety ofPayload - The query parameters taken from
"query"/"queries"/"queryParams"/"queryParameters"
value from thePayload - The body from the
GraphQLRequest
#
Payload
The connection params is given during websocket initialization from payload
as part of ConnectionInit
message inside an established WebSocket connection.
Given that the payload
parameter is custom each client, it does not have any strong typing, so you would have to work with Map
enum.
func someHeader(ctx: Context, _: NoArguments) async -> String? {
guard .string(let token) = ctx.params?["Authorization"] else { ... }
return token
}
#
GraphQLRequest
This is operation specific graphql request / query given an operation is being executed.
func someHeader(ctx: Context, _: NoArguments) throws -> String? {
switch try ctx.gql.operationType() {
case .subscription:
...
case .query:
...
case .mutation:
...
}
}
#
WebSocket Guard
There might be times where you want to authorize any incoming WebSocket connection before any operation done, and thus before the context builder is executed.
Pioneer's Vapor integration provide a way to run custom code during the GraphQL over WebSocket initialisation phase that can deny a WebSocket connection by throwing an error.
app.middleware.use(
server.vaporMiddleware(
context: { req, res in
...
},
websocketContext: { req, payload, gql in
...
},
websocketGuard: { req, payload in
...
}
)
)
#
Handlers
Pioneer's Vapor also exposes the HTTP handlers for GraphQL over HTTP operations, GraphQL over WebSocket upgrade, and GraphQL IDE hosting.
This allow opting out of the middleware for integrating Pioneer and Vapor, by manually setting this handlers on routes.
#
GraphQL IDE hosting
.ideHandler will serve incoming request with the configured GraphQL IDE.
app.group("graphql") { group in
group.get { req in
server.ideHandler(req: req)
}
}
#
GraphQL over HTTP operations
.httpHandler will execute a GraphQL operation and return a well-formatted response.
app.group("graphql") { group in
group.post { req in
try await server.httpHandler(
req: req,
context: { req, res in
...
}
)
}
}
#
GraphQL over WebSocket upgrade
.webSocketHandler will upgrade incoming request into a WebSocket connection and start the process of GraphQL over Websocket.
app.group("graphql") { group in
group.get("ws") { req in
try await server.webSocketHandler(
req: req,
context: { req, payload, gql in
...
},
guard: { req, payload in
...
}
)
}
}
#
Additional benefits
The Vapor integration include other benefits such as:
- Includes all security measurements done by Pioneer automatically (i.e. CSRF Prevention)
- Automatically operation check for HTTP methods using the given HTTPStrategy
- Extensions for
CORSMiddleware.Configuration
for allowing Cloud based GraphQL IDEs