Realtime
One of the most often-asked questions of Redwood before and after the launch of V1 was, “When will Redwood support a realtime solution?”
The answer is: now.
What is Realtime?
Redwood's initial realtime solution leverages GraphQL and relies on a serverful deployment to maintain a long-running connection between the client and server.
This means that your cannot use Realtime when deploying to Netlify or Vercel.
See one of Redwood's many other Deploy providers, and the Docker setup for good measure.
Redwood's GraphQL server uses the GraphQL over Server-Sent Events spec's "distinct connections mode" for subscriptions.
Advantages of SSE over WebSockets include:
- Transported over simple HTTP instead of a custom protocol
- Built in support for re-connection and event-id
- Simpler protocol
- No trouble with corporate firewalls doing packet inspection
Subscriptions and Live Queries
In GraphQL, there are two options for real-time updates: live queries and subscriptions.
Subscriptions are part of the GraphQL specification, whereas live queries are not.
There are times where subscriptions are well-suited for a realtime problem and in some cases live queries may be a better fit. Later we’ll explore the pros and cons of each approach and how best to decide which to use and when.
Defer and Stream
Defer and stream are directives that allow you to improve latency for clients by sending the most important data as soon as it's ready.
As applications grow, the GraphQL operation documents can get bigger. The server will only send the response back once all the data requested in the query is ready. But not all requested data is of equal importance, and the client may not need all of the data at once.
Using Defer
The @defer directive allows you to postpone the delivery of one or more (slow) fields grouped in an inlined or spread fragment.
Using Stream
The @stream directive allows you to stream the individual items of a field of the list type as the items are available.
The @stream directive is currently not supported by Apollo GraphQL client.
Features
Realtime handles the hard parts of a GraphQL realtime implementation by automatically:
- allowing GraphQL Subscription operations to be handled
- merging in your subscriptions types and mapping their handler functions (subscribe and resolve) to your GraphQL schema letting you keep your subscription logic organized and apart from services (your subscription may use a service to respond to an event)
- authenticating subscription requests using the same
@requireAuthdirectives already protecting other queries and mutations (or you can implement your own validator directive) - adding in the
@livequery directive to your GraphQL schema and setting up theuseLiveQueryenvelop plugin to handle requests, invalidation, and managing the storage mechanism needed - creating and configuring in-memory and persisted Redis stores used by the PubSub transport for subscriptions and Live Queries (and letting you switch between them in development and production)
- placing the pubSub transport and stores into the GraphQL context so you can use them in services, subscription resolvers, or elsewhere (like a webhook, function, or job) to publish an event or invalidate data
- typing your subscription channel event payloads
- support
@deferand@streamdirectives
It provides a first-class developer experience for real-time updates with GraphQL so you can easily
- respond to an event (e.g. NewPost, NewUserNotification)
- respond to a data change (e.g. Post 123's title updated)
and have the latest data reflected in your app.
Lastly, the Redwood CLI has commands to generate a boilerplate implementation and sample code needed to create your custom subscriptions and Live Queries.
Regardless of the implementation chosen, a stateful server and store are needed to track changes, invalidation, and who wants to be informed about changes.
What can I build with Realtime?
- Application alerts and messages
- User notifications
- Live charts
- Location updates
- Auction bid updates
- Messaging
- OpenAI streaming responses
Redwood Realtime Setup
To setup realtime in an existing Redwood project, run the following commands:
yarn rw setup server-fileyarn rw setup realtime
You'll get:
api/server.tswhere you can configure your Fastify serverapi/lib/realtime.tswhere you consume your subscriptions and configure realtime with an in-memory or Redis store- Usage examples for live queries, subscriptions, defer, and stream. You'll get sdl, services/subscriptions for each
- The
auctionlive query example - The
countdown timersubscription example - The
chatsubscription examples - The
alphabetstream example - The
slow and fastfield defer example
There is no UI set up for these examples. You can find information on how to try them out using the GraphiQL playground.
Just add the realtime configuration to your GraphQL handler in api/src/functions/graphql.ts and you're good to go:
+ import { realtime } from 'src/lib/realtime'
export const handler = createGraphQLHandler({
// ...
+ realtime,
})
Realtime Configuration
By default, Redwood's realtime configures an in-memory store for the Pub Sub client used with subscriptions and live query invalidation.
Realtime supports in-memory and Redis stores:
- In-memory stores are useful for development and testing.
- Redis stores are useful for production.
To enable defer and streaming, set enableDeferStream to true.
Configure a Redis store and defer and stream in:
import { RedwoodRealtimeOptions } from '@cedarjs/realtime'
import subscriptions from 'src/subscriptions/**/*.{js,ts}'
// if using a Redis store
// import { Redis } from 'ioredis'
// const publishClient = new Redis()
// const subscribeClient = new Redis()
/**
* Configure RedwoodJS Realtime
*
* See https://redwoodjs.com/docs/realtime
*
* Realtime supports Live Queries and Subscriptions over GraphQL SSE.
*
* Live Queries are GraphQL queries that are automatically re-run when the data they depend on changes.
*
* Subscriptions are GraphQL queries that are run when a client subscribes to a channel.
*
* Redwood Realtime
* - uses a publish/subscribe model to broadcast data to clients.
* - uses a store to persist Live Query and Subscription data.
*
* Redwood Realtime supports in-memory and Redis stores:
* - In-memory stores are useful for development and testing.
* - Redis stores are useful for production.
*/
export const realtime: RedwoodRealtimeOptions = {
subscriptions: {
subscriptions,
store: 'in-memory',
// if using a Redis store
// store: { redis: { publishClient, subscribeClient } },
},
liveQueries: {
store: 'in-memory',
// if using a Redis store
// store: { redis: { publishClient, subscribeClient } },
},
// To enable defer and streaming, set to true.
// enableDeferStream: true,
}
PubSub and LiveQueryStore
By setting up realtime, the GraphQL server adds two helpers on the context:
- pubSub
- liveQueryStory
With context.pubSub you can subscribe to and publish messages via context.pubSub.publish('the-topic', id, id2).
With context.liveQueryStore. you can context.liveQueryStore.invalidate(key) where your key may be a reference or schema coordinate:
Reference
Where the query is: auction(id: ID!): Auction @requireAuth:
"Auction:123"
Schema Coordinate
When the query is: auctions: [Auction!]! @requireAuth:
"Query.auctions"
Subscriptions
Redwood has a first-class developer experience for GraphQL subscriptions.
Subscribe to Events
- Granular information on what data changed
- Why has the data changed?
- Spec compliant
Chat/New Message Example
type Subscription {
newMessage(roomId: ID!): Message! @requireAuth
}
- I subscribed to a "newMessage” in room “2”
- Someone added a message to room “2” with a from and body
- A "NewMessage" event to Room 2 gets published
- I find out and see who the message is from and what they messaged (the body)
Countdown Timer Example
Counts down from a starting values by an interval.
subscription CountdownFromInterval {
countdown(from: 100, interval: 10)
}
This example showcases how a subscription yields its own response.