# 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.

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.

Getting a cookie example
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.

Setting a cookie example
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 Request, Payload, and GraphQLRequest.

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.

Getting Fluent DB or EventLoop
struct Resolver {
    func something(ctx: Context, _: NoArguments) async -> [User] {
        return User.query(on: ctx.req.db).all()
    }
}

The custom request will similar to the request used to upgrade to websocket but will have:

  • The headers taken from "header"/"headers" value from the Payload or all the entirety of Payload
  • The query parameters taken from "query"/"queries"/"queryParams"/"queryParameters" value from the Payload
  • 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.

Getting some values
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.

Getting operation type
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