In a Startup
class...
namespace MyApp
open Giraffe
open FSharp.Data.GraphQL.Server.AspNetCore.Giraffe
open FSharp.Data.GraphQL.Server.AspNetCore
open Microsoft.AspNetCore.Server.Kestrel.Core
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Configuration
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
open System
open System.Text.Json
type Startup private () =
// Factory for object holding request-wide info. You define Root somewhere else.
let rootFactory () : Root =
{ RequestId = Guid.NewGuid().ToString() }
new (configuration: IConfiguration) as this =
Startup() then
this.Configuration <- configuration
member _.ConfigureServices(services: IServiceCollection) =
services.AddGiraffe()
.AddGraphQL<Root>( // STEP 1: Setting the options
Schema.executor, // --> Schema.executor is defined by yourself somewhere else (in another file)
rootFactory,
"/ws" // --> endpoint for websocket connections (optional. Default value: "/ws")
)
|> ignore
member _.Configure(app: IApplicationBuilder, applicationLifetime : IHostApplicationLifetime, loggerFactory : ILoggerFactory) =
let errorHandler (ex : Exception) (log : ILogger) =
log.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.")
clearResponse >=> setStatusCode 500
app
.UseGiraffeErrorHandler(errorHandler)
.UseWebSockets()
.UseWebSocketsForGraphQL<Root>() // STEP 2: using the GraphQL websocket middleware
.UseGiraffe (HttpHandlers.handleGraphQL<Root>)
member val Configuration : IConfiguration = null with get, set
In your schema, you'll want to define a subscription, like in (example taken from the star-wars-api sample in the "samples/" folder):
let Subscription =
Define.SubscriptionObject<Root>(
name = "Subscription",
fields = [
Define.SubscriptionField(
"watchMoon",
RootType,
PlanetType,
"Watches to see if a planet is a moon.",
[ Define.Input("id", StringType) ],
(fun ctx _ p -> if ctx.Arg("id") = p.Id then Some p else None)) ])
Don't forget to notify subscribers about new values:
let Mutation =
Define.Object<Root>(
name = "Mutation",
fields = [
Define.Field(
"setMoon",
Nullable PlanetType,
"Defines if a planet is actually a moon or not.",
[ Define.Input("id", StringType); Define.Input("isMoon", BooleanType) ],
fun ctx _ ->
getPlanet (ctx.Arg("id"))
|> Option.map (fun x ->
x.SetMoon(Some(ctx.Arg("isMoon"))) |> ignore
schemaConfig.SubscriptionProvider.Publish<Planet> "watchMoon" x // here you notify the subscribers upon a mutation
x))])
Finally run the server (e.g. make it listen at localhost:8086
).
There's a demo chat application backend in the samples/chat-app
folder that showcases the use of FSharp.Data.GraphQL.Server.AspNetCore
in a real-time application scenario, that is: with usage of GraphQL subscriptions (but not only).
The tried and trusted star-wars-api
also shows how to use subscriptions, but is a more basic example in that regard. As a side note, the implementation in star-wars-api
was used as a starting point for the development of FSharp.Data.GraphQL.Server.AspNetCore
.
Using your favorite (or not :)) client library (e.g.: Apollo Client, Relay, Strawberry Shake, elm-graphql ❤️), just point to localhost:8086/graphql
(as per the example above) and, as long as the client implements the graphql-transport-ws
subprotocol, subscriptions should work.