diff --git a/docs/docs/app-configuration-redwood-toml.md b/docs/docs/app-configuration-redwood-toml.md index fee88a7b9cdc..4439e88ea3db 100644 --- a/docs/docs/app-configuration-redwood-toml.md +++ b/docs/docs/app-configuration-redwood-toml.md @@ -5,7 +5,7 @@ description: Configure your app with redwood.toml # App Configuration: redwood.toml -You can configure your Redwood app in `redwood.toml`. By default, `redwood.toml` lists the following configuration options: +One of the premier places you can configure your Redwood app is `redwood.toml`. By default, `redwood.toml` lists the following configuration options: ```toml title="redwood.toml" [web] @@ -23,36 +23,30 @@ You can configure your Redwood app in `redwood.toml`. By default, `redwood.toml` These are listed by default because they're the ones that you're most likely to configure, but there are plenty more available. -The options and their structure are based on Redwood's notion of sides and targets. Right now, Redwood has two sides, api and web, that target Node.js Lambdas and browsers respectively. In the future, we'll add support for more sides and targets, and as we do, you'll see them reflected in `redwood.toml`. - -> For the difference between a side and a target, see [Redwood File Structure](tutorial/chapter1/file-structure.md). - You can think of `redwood.toml` as a frontend for configuring Redwood's build tools. -For certain options, instead of having to deal with build tools configuration directly, there's quick access via `redwood.toml`. +For certain options, instead of having to configure build tools directly, there's quick access via `redwood.toml`. ## [web] | Key | Description | Default | | :---------------------------- | :--------------------------------------------------------- | :---------------------- | -| `apiUrl` | The path or URL to your api-server | `"/.redwood/functions"` | -| `apiGraphQLUrl` | The path or URL to your GraphQL function | `"${apiUrl}/graphql"` | -| `apiDbAuthUrl` | The path or URL to your dbAuth function | `"${apiUrl}/auth"` | -| `a11y` | Enable storybook `addon-a11y` and `eslint-plugin-jsx-a11y` | `true` | -| `host` | Hostname to listen on | `"localhost"` | -| `includeEnvironmentVariables` | Environment variables to include | `[]` | -| `path` | Path to the web side | `"./web"` | -| `port` | Port to listen on | `8910` | +| `title` | Title of your Redwood app | `'Redwood App'` | +| `port` | Port for the web server to listen at | `8910` | +| `apiUrl` | URL to your api server. This can be a relative URL in which case it acts like a proxy, or a fully-qualified URL | `'/.redwood/functions'` | +| `includeEnvironmentVariables` | Environment variables made available to the web side during dev and build | `[]` | +| `host` | Hostname for the web server to listen at | Defaults to `'0.0.0.0'` in production and `'::'` in development | +| `apiGraphQLUrl` | URL to your GraphQL function | `'${apiUrl}/graphql'` | +| `apiDbAuthUrl` | URL to your dbAuth function | `'${apiUrl}/auth'` | | `sourceMap` | Enable source maps for production builds | `false` | -| `target` | Target for the web side | `"browser"` | -| `title` | Title of your Redwood app | `"Redwood App"` | +| `a11y` | Enable storybook `addon-a11y` and `eslint-plugin-jsx-a11y` | `true` | ### Customizing the GraphQL Endpoint -By default, Redwood derives the GraphQL endpoint from `apiUrl` such that `./redwood/functions/graphql` ends up being the default graphql endpoint. -But sometimes you want to host your api side somewhere else, or even on a different domain. +By default, Redwood derives the GraphQL endpoint from `apiUrl` such that it's `${apiUrl}/graphql`, (with the default `apiUrl`, `./redwood/functions/graphql`). +But sometimes you want to host your api side somewhere else. There's two ways you can do this: -1. Change `apiUrl` to a different domain: +1. Change `apiUrl`: ```toml title="redwood.toml" [web] @@ -61,32 +55,36 @@ There's two ways you can do this: Now the GraphQL endpoint is at `https://api.coolredwoodapp.com/graphql`. -2. Only change the GraphQL endpoint: +2. Change `apiGraphQLUrl`: ```diff title="redwood.toml" -[web] - apiUrl = "/.redwood/functions" -+ apiGraphqlEndpoint = "https://coolrwapp.mycdn.com" + [web] + apiUrl = "/.redwood/functions" ++ apiGraphQLUrl = "https://api.coolredwoodapp.com/graphql" ``` ### Customizing the dbAuth Endpoint -If you're using dbAuth, you may decide to point its function at a different host. -To do this without affecting your GraphQL endpoint, you can add `apiDbAuthUrl` to your `redwood.toml`: +Similarly, if you're using dbAuth, you may decide to host it somewhere else. +To do this without affecting your other endpoints, you can add `apiDbAuthUrl` to your `redwood.toml`: ```diff title="redwood.toml" -[web] - apiUrl = "/.redwood/functions" -+ apiDbAuthUrl = "https://api.mycoolapp.com/auth" + [web] + apiUrl = "/.redwood/functions" ++ apiDbAuthUrl = "https://api.coolredwoodapp.com/auth" ``` -> If you point your web side to a different domain, please make sure you have [CORS headers](cors.md) configured. -> Otherwise browser security features may block requests from the client. +:::tip + +If you host your web and api sides at different domains and don't use a proxy, make sure you have [CORS](./cors.md) configured. +Otherwise browser security features may block client requests. + +::: ### includeEnvironmentVariables -`includeEnvironmentVariables` is the set of environment variables to include in the web side. -Use it to include environment variables you've defined in `.env`: +`includeEnvironmentVariables` is the set of environment variables that should be available to your web side during dev and build. +Use it to include env vars like public keys for third-party services you've defined in your `.env` file: ```toml title="redwood.toml" [web] @@ -99,203 +97,20 @@ PUBLIC_KEY=... Instead of including them in `includeEnvironmentVariables`, you can also prefix them with `REDWOOD_ENV_` (see [Environment Variables](environment-variables.md#web)). -## [api] - -| Key | Description | Default | -| :------------- | :---------------------------------- | :------------------------- | -| `debugPort` | Port to expose for the debugger | `18911` | -| `host` | Hostname to listen on | `"localhost"` | -| `path` | Path to the api side | `"./api"` | -| `port` | Port to listen on | `8911` | -| `serverConfig` | Path to the `server.config.js` file | `"./api/server.config.js"` | -| `target` | Target for the api side | `"node"` | - -### Configure Fastify - -You can configure the Fastify server instance in `api/server.config.js`. -For all the configuration options, see [Fastify's docs](https://www.fastify.io/docs/latest/Reference/Server/#factory). +:::caution `includeEnvironmentVariables` isn't for secrets -:::info Where does this configuration apply? - -This configuration does **not** apply in a serverless deploy. -Typically when you deploy to a serverless provider like Netlify or Vercel, your project's web side is served from a CDN, and functions are invoked directly. -But this configuration does apply when running: - -| Command | api | web | -| :-------------- | :--- | :--- | -| `yarn rw dev` | ✅ | ❌ | -| `yarn rw serve` | ✅ | ✅ | +Don't make secrets available to your web side. Everything in `includeEnvironmentVariables` is included in the bundle. ::: -Using redwood.toml's [env var interpolation](#using-environment-variables-in-redwoodtoml), you can change the server config used based on your deployment environment: - -```toml title="redwood.toml" -[api] - serverConfig = "./api/${DEPLOY_ENVIRONMENT}-server.config.js" -``` - -### Register Custom Fastify Plugins - -You can register Fastify plugins for the api and web sides using the `configureFastify` function. -This function has access to the Fastify server instance and options, such as the side that's being configured. - -:::warning Reminder - -This configuration does **not** apply in a serverless deploy. - -::: - -```js -/** @type {import('@redwoodjs/api-server/dist/fastify').FastifySideConfigFn} */ -const configureFastify = async (fastify, options) => { - if (options.side === 'api') { - fastify.log.trace({ custom: { options } }, 'Configuring api side') - } - - if (options.side === 'web') { - fastify.log.trace({ custom: { options } }, 'Configuring web side') - } - - return fastify -} -``` - -#### How to configure a Fastify plugin for the api side - -Let's say that you want to compress payloads and rate limit your API. -You can leverage two Fastify ecosystem plugins, [@fastify/compress](https://github.com/fastify/fastify-compress) and [@fastify/rate-limit](https://github.com/fastify/fastify-rate-limit) respectively. - -Here, we configure compression so that it handles all requests, compresses responses only if they're larger than 1K, and to prefer the `deflate` method over `gzip`. -Using @fastify/rate-limit, we allow an IP address to only make 100 requests in a five minute window. - -:::important Plugins need to be installed - -You'll need to install plugin packages in your project's `api` workspace: - -``` -yarn workspace api add @fastify/rate-limit @fastify/compress -``` - -::: - -```js -/** @type {import('@redwoodjs/api-server/dist/fastify').FastifySideConfigFn} */ -const configureFastify = async (fastify, options) => { - if (options.side === 'api') { - fastify.log.trace({ custom: { options } }, 'Configuring api side') - - await fastify.register(import('@fastify/compress'), { - global: true, - threshold: 1_024, - encodings: ['deflate', 'gzip'], - }) - - await fastify.register(import('@fastify/rate-limit'), { - max: 100, - timeWindow: '5 minutes', - }) - } - - return fastify -} -``` - -#### How to Configure a Fastify plugin for the web side - -If you're running the web side using `yarn rw serve`, you can configure plugins like [@fastify/etag](https://github.com/fastify/fastify-etag) to register HTTP Etags. - -:::important Plugins need to be installed - -You'll need to install plugin packages in your project's `api` workspace. -This may seem counter-intuitive, since you're configuring the `web` side, but the `api-server` gets configured in your project's `api` side and that's what's serving web assets. - -::: - -```js -/** @type {import('@redwoodjs/api-server/dist/fastify').FastifySideConfigFn} */ -const configureFastify = async (fastify, options) => { - if (options.side === 'web') { - fastify.log.trace({ custom: { options } }, 'Configuring web side') - - fastify.register(import('@fastify/etag')) - } - - return fastify -} -``` - -#### Troubleshooting Custom Fastify Configuration - -There are a few important things to consider when configuring Fastify. - -If running via `yarn rw serve`, only register a plugin once either in `api` or in `web`. Registering the same plugin in both sides will error saying that it has already been registered. - -Running via `yarn rw serve` uses a single Fastify instance to serve both api functions and web assets, so registering the plugin in a single side applies it to that instance. - -### How to Configure Fastify to Accept File Uploads - -If you try to POST file content to the api server such as images or PDFs, you may see the following error from Fastify: - -```json -{ - "statusCode": 400, - "code": "FST_ERR_CTP_INVALID_CONTENT_LENGTH", - "error": "Bad Request", - "message": "Request body size did not match Content-Length" -} -``` - -This's because Fastify [only supports `application/json` and `text/plain` content types natively](https://www.fastify.io/docs/latest/Reference/ContentTypeParser/). -While Redwood configures the api server to also accept `application/x-www-form-urlencoded` and `multipart/form-data`, if you want to support other content or MIME types (likes images or PDFs), you'll need to configure them yourself. - -You can use Fastify's `addContentTypeParser` function to allow uploads of the content types your application needs. -For example, to support image file uploads you'd tell Fastify to allow `/^image\/.*/` content types: - -```js -/** @type {import('@redwoodjs/api-server/dist/fastify').FastifySideConfigFn} */ -const configureFastify = async (fastify, options) => { - if (options.side === 'api') { - fastify.log.trace({ custom: { options } }, 'Configuring api side') - - fastify.addContentTypeParser(/^image\/.*/, (req, payload, done) => { - payload.on('end', () => { - done() - }) - }) - } - - return fastify -} -``` - -:::note - -The above regular expression (`/^image\/.*/`) allows all image content or MIME types because [they start with "image"](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types). - -::: - -Now, when you POST those content types to a function served by the api server, you can access the file content on `event.body`: - -```bash -curl --location --request POST 'http://localhost:8911/upload' \ - --form 'image=@"/path/to/my/image/web/public/favicon.png"' \ - --header 'Content-Type: image/png' -``` - -```terminal -api | 17:38:49 🌲 request completed 0ms -api | 17:38:49 🐛 body -api | 🗒 Custom -api | "--------------------------e66d9a27b7c2b271\r\nContent-Disposition: attachment; name=\"image\"; filename=\"favicon.png\"\r\nContent-Type: image/png\r\n\r\n�PNG\r\n\u001a\n\u0000\u0000\u0000\rIHDR\u0000\u0000\u0000 \u0000\u0000\u0000`�\r\n--------------------------e66d9a27b7c2b271--\r\n" -``` - -:::warning File uploads only work in a serverful deploy - -Serverless functions on Netlify or Vercel do not use this Fastify configuration. -They also have memory and execution time limits that don't lend themselves to handling file uploads of any practical size. +## [api] -::: +| Key | Description | Default | +| :------------- | :---------------------------------- | :------------------------- | +| `port` | Port for the api server to listen at | `8911` | +| `host` | Hostname for the api server to listen at | Defaults to `'0.0.0.0'` in production and `'::'` in development | +| `debugPort` | Port for the debugger to listen at | `18911` | +| `serverConfig` | [Deprecated; use the [server file](./docker.md#using-the-server-file) instead] Path to the `server.config.js` file | `'./api/server.config.js'` | ## [browser] @@ -304,11 +119,11 @@ They also have memory and execution time limits that don't lend themselves to ha open = true ``` -Setting `open` to `true` opens your browser to `${host}:${port}` (by default, `localhost:8910`) after the dev server starts. -If you want your browser to stop opening when you `yarn rw dev`, set this to false. +Setting `open` to `true` opens your browser to `http://${web.host}:${web.port}` (by default, `http://localhost:8910`) after the dev server starts. +If you want your browser to stop opening when you run `yarn rw dev`, set this to `false`. (Or just remove it entirely.) -There's actually a lot more you can do here. For more, see Vite's docs on [preview.open](https://vitejs.dev/config/preview-options.html#preview-open). +There's actually a lot more you can do here. For more, see Vite's docs on [`preview.open`](https://vitejs.dev/config/preview-options.html#preview-open). ## [generate] @@ -318,9 +133,9 @@ There's actually a lot more you can do here. For more, see Vite's docs on [previ stories = true ``` -Many of Redwood's generators create Jest test or Storybook files. +Many of Redwood's generators create Jest tests or Storybook stories. Understandably, this can be lot of files, and sometimes you don't want all of them, either because you don't plan on using Jest or Storybook, or are just getting started and don't want the overhead. -These toml keys allows you to toggle the generation of test or story files. +These options allows you to disable the generation of test and story files. ## [cli] @@ -329,10 +144,10 @@ These toml keys allows you to toggle the generation of test or story files. versionUpdates = ["latest"] ``` -There's new versions of the framework all the time—a major every couple months, a minor every week or two, and patches when appropriate. +There are new versions of the framework all the time—a major every couple months, a minor every week or two, and patches when appropriate. And if you're on an experimental release line, like canary, there's new versions every day, multiple times. -If you'd like to get notified (at most, once a day) when there's a new version, set `versionUpdates` to include the version tags you're interested in within `redwood.toml`'s `notifications` table. +If you'd like to get notified (at most, once a day) when there's a new version, set `versionUpdates` to include the version tags you're interested in. ## Using Environment Variables in `redwood.toml` @@ -374,3 +189,6 @@ To run a Redwood app in a container or VM, you'll want to set both the web and a [api] host = '0.0.0.0' ``` + +You can also configure these values via `REDWOOD_WEB_HOST` and `REDWOOD_API_HOST`. +And if you set `NODE_ENV` to production, these will be the defaults anyway. diff --git a/docs/docs/docker.md b/docs/docs/docker.md index 664d0d72087b..33c2e65bd711 100644 --- a/docs/docs/docker.md +++ b/docs/docs/docker.md @@ -261,7 +261,7 @@ We need to grab it too. ```Dockerfile ENV NODE_ENV=production -CMD [ "node_modules/.bin/rw-server", "api", "--load-env-files" ] +CMD [ "node_modules/.bin/rw-server", "api" ] ``` Lastly, the default command is to start the api server using the bin from the `@redwoodjs/api-server` package. @@ -466,3 +466,215 @@ yarn why @supabase/supabase-js ``` In this case, it looks like it's ultimately because of our auth provider, `@supabase/supabase-js`. + +## Using the Server File + +Redwood v7 introduced a new entry point to Redwood's api server: the server file at `api/src/server.ts`. +The server file was made with Docker in mind. It allows you to + +1. have control over how the api server starts, +2. customize the server as much as you want, and +3. minimize the number of dependencies needed to start the api server process (all you need is Node.js!) + +Get started by running the setup command: + +``` +yarn rw setup server-file +``` + +This should give you a new file at `api/src/server.ts`: + +```typescript title="api/src/server.ts" +import { createServer } from '@redwoodjs/api-server' + +import { logger } from 'src/lib/logger' + +async function main() { + const server = await createServer({ + logger, + }) + + await server.start() +} + +main() +``` + +Without the server file, to start the api side, you'd use binaries provided by `@redwoodjs/api-server` such as `yarn rw-server api` (you may also see this as `./node_modules/.bin/rw-server api`). + +With the server file, there's no indirection. Just use `node`: + +``` +yarn node api/dist/server.js +``` + +:::info You have to build first + +You can't run the server file directly with Node.js; it has to be built first: + +``` +yarn rw build api +``` + +The api serve stage in the Dockerfile pulls from the api build stage, so things are already in the right order there. Similarly, for `yarn rw dev`, the dev server will build and reload the server file for you. + +::: + +That means you can swap the `CMD` instruction in the api server stage: + +```diff + ENV NODE_ENV=production + +- CMD [ "node_modules/.bin/rw-server", "api" ] ++ CMD [ "yarn", "node", "api/dist/server.js" ] +``` + +### Configuring the server + +There's two ways you can configure the server. + +First, you can configure how the underlying Fastify server is instantiated via the`fastifyServerOptions` passed to the `createServer` function: + +```ts title="api/src/server.ts" +const server = await createServer({ + logger, + // highlight-start + fastifyServerOptions: { + // ... + } + // highlight-end +}) +``` + +For the complete list of options, see [Fastify's documentation](https://fastify.dev/docs/latest/Reference/Server/#factory). + +Second, you can register Fastify plugins on the server instance: + +```ts title="api/src/server.ts" +const server = await createServer({ + logger, +}) + +// highlight-next-line +server.register(myFastifyPlugin) +``` + +#### Example: Compressing Payloads and Rate Limiting + +Let's say that we want to compress payloads and add rate limiting. +We want to compress payloads only if they're larger than 1KB, preferring deflate to gzip, +and we want to limit IP addresses to 100 requests in a five minute window. +We can leverage two Fastify ecosystem plugins, [@fastify/compress](https://github.com/fastify/fastify-compress) and [@fastify/rate-limit](https://github.com/fastify/fastify-rate-limit) respectively. + +First, you'll need to install these packages: + +``` +yarn workspace api add @fastify/compress @fastify/rate-limit +``` + +Then register them with the appropriate config: + +```ts title="api/src/server.ts" +const server = await createServer({ + logger, +}) + +await server.register(import('@fastify/compress'), { + global: true, + threshold: 1024, + encodings: ['deflate', 'gzip'], +}) + +await server.register(import('@fastify/rate-limit'), { + max: 100, + timeWindow: '5 minutes', +}) +``` + +#### Example: File Uploads + +If you try to POST file content to the api server such as images or PDFs, you may see the following error from Fastify: + +```json +{ + "statusCode": 400, + "code": "FST_ERR_CTP_INVALID_CONTENT_LENGTH", + "error": "Bad Request", + "message": "Request body size did not match Content-Length" +} +``` + +This's because Fastify [only supports `application/json` and `text/plain` content types natively](https://www.fastify.io/docs/latest/Reference/ContentTypeParser/). +While Redwood configures the api server to also accept `application/x-www-form-urlencoded` and `multipart/form-data`, if you want to support other content or MIME types (likes images or PDFs), you'll need to configure them here in the server file. + +You can use Fastify's `addContentTypeParser` function to allow uploads of the content types your application needs. +For example, to support image file uploads you'd tell Fastify to allow `/^image\/.*/` content types: + +```ts title="api/src/server.ts" +const server = await createServer({ + logger, +}) + +server.addContentTypeParser(/^image\/.*/, (req, payload, done) => { + payload.on('end', () => { + done() + }) +}) +``` + +The regular expression (`/^image\/.*/`) above allows all image content or MIME types because [they start with "image"](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types). + +Now, when you POST those content types to a function served by the api server, you can access the file content on `event.body`. + +### The `start` method + +Since there's a few different ways to configure the host and port the server listens at, the server instance returned by `createServer` has a special `start` method: + +```ts title="api/src/server.ts" +await server.start() +``` + +`start` is a thin wrapper around [`listen`](https://fastify.dev/docs/latest/Reference/Server/#listen). +It takes the same arguments as `listen`, except for host and port. It computes those in the following way, in order of precedence: + +1. `--host` or `--port` flags: + + ``` + yarn node api/dist/server.js --host 0.0.0.0 --port 8913 + ``` + +2. `REDWOOD_API_HOST` or `REDWOOD_API_PORT` env vars: + + ``` + export REDWOOD_API_HOST='0.0.0.0' + export REDWOOD_API_PORT='8913' + yarn node api/dist/server.js + ``` + +3. `[api].host` and `[api].port` in `redwood.toml`: + + ```toml title="redwood.toml" + [api] + host = '0.0.0.0' + port = 8913 + ``` + +If you'd rather not have `createServer` parsing `process.argv`, you can disable it via `parseArgv`: + +```ts title="api/src/server.ts" +await createServer({ + parseArgv: false, +}) +``` + +And if you'd rather it do none of this, just change `start` to `listen` and specify the host and port inline: + +```ts title="api/src/server.ts" +await server.listen({ + host: '0.0.0.0', + port: 8913, +}) +``` + +If you don't specify a host, `createServer` uses `NODE_ENV` to set it. If `NODE_ENV` is production, it defaults to `'0.0.0.0'` and `'::'` otherwise. +The Dockerfile sets `NODE_ENV` to production so that things work out of the box. diff --git a/docs/docs/realtime.md b/docs/docs/realtime.md index a1259ad769c1..3a247671160e 100644 --- a/docs/docs/realtime.md +++ b/docs/docs/realtime.md @@ -1,20 +1,22 @@ # Realtime -One of the most often asked questions of RedwoodJS before and after the launch of V1 was, “When will RedwoodJS support a realtime solution?” +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? -RedwoodJS's initial real-time solution leverages GraphQL and relies on a serverful deployment to maintain a long-running connection between the client and server. +Redwood's initial realtime solution leverages GraphQL and relies on a serverful deployment to maintain a long-running connection between the client and server. -:::note -This means that your cannot currently use RedwoodJS Realtime when deployed to Netlify or Vercel. +:::info + +This means that your cannot use Realtime when deploying to Netlify or Vercel. + +See one of Redwood's many [other Deploy providers](./deploy/introduction.md), and the [Docker setup](./docker.md) for good measure. -**More information about deploying a serverful RedwoodJS application is forthcoming.** ::: -RedwoodJS's GraphQL Server uses [GraphQL over Server-Sent Events](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md#distinct-connections-mode) spec "distinct connections mode" for subscriptions. +Redwood's GraphQL server uses the [GraphQL over Server-Sent Events](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md#distinct-connections-mode) spec's "distinct connections mode" for subscriptions. Advantages of SSE over WebSockets include: @@ -29,21 +31,21 @@ In GraphQL, there are two options for real-time updates: **live queries** and ** 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 that to use and when. +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 -[Stream and defer](https://the-guild.dev/graphql/yoga-server/docs/features/defer-stream) are directives that allow you to improve latency for clients by sending the most important data as soon as it's ready. +[Defer and stream](https://the-guild.dev/graphql/yoga-server/docs/features/defer-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. However, not all requested data is of equal importance, and the client may not need all of the data at once. +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 post-pone the delivery of one or more (slow) fields grouped in an inlined or spread fragment. +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 allows you to stream the individual items of a field of the list type as the items are available. :::info The `@stream` directive is currently **not** supported by Apollo GraphQL client. @@ -51,15 +53,15 @@ The `@stream` directive is currently **not** supported by Apollo GraphQL client. ## Features -RedwoodJS Realtime handles the hard parts of a GraphQL Realtime implementation by automatically: +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 my use a service to respond to an event) +- 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 `@requireAuth` directives already protecting other queries and mutations (or you can implement your own validator directive) - adding in the `@live` query directive to your GraphQL schema and setting up the `useLiveQuery` envelop plugin to handle requests, invalidation, and managing the storage mechanism needed -- creating and configuring in-memory and persisted Redis stores uses by the PubSub transport for subscriptions and Live Queries (and letting you switch between them in development and production) +- 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 you subscription channel event payloads +- typing your subscription channel event payloads - support `@defer` and `@stream` directives It provides a first-class developer experience for real-time updates with GraphQL so you can easily @@ -69,36 +71,32 @@ It provides a first-class developer experience for real-time updates with GraphQ 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 - - live Queries +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, or who wants to be informed about the change. +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 +- Application alerts and messages +- User notifications +- Live charts - Location updates - Auction bid updates - Messaging - OpenAI streaming responses -## RedwoodJS Realtime Setup +## Redwood Realtime Setup -To setup Realtime in an existing RedwoodJS project, run the following commands: +To setup realtime in an existing Redwood project, run the following commands: -* `yarn rw exp setup-server-file` -* `yarn rw exp setup-realtime` +* `yarn rw setup server-file` +* `yarn rw setup realtime` -You will get: +You'll get: -* `api/server.ts` where you configure your Fastify server and GraphQL +* `api/server.ts` where you can configure your Fastify server * `api/lib/realtime.ts` where 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. +* Usage examples for live queries, subscriptions, defer, and stream. You'll get sdl, services/subscriptions for each * The [`auction` live query](#auction-live-query-example) example * The [`countdown timer` subscription](#countdown-timer-example) example * The [`chat` subscription](#chatnew-message-example) examples @@ -106,49 +104,23 @@ You will get: * The [`slow and fast` field defer](#slow-and-fast-field-defer-example) example :::note -There is no UI setup for these examples. You can find information on how to try them out using the GraphiQL playground. +There is no UI set up for these examples. You can find information on how to try them out using the GraphiQL playground. ::: -### GraphQL Configuration - -Now that how have a serverful project, you will configure your GraphQL server in the `api/server.ts` file. +Just add the realtime configuration to your GraphQL handler in `api/src/functions/graphql.ts` and you're good to go: -:::important -That means you **must** manually configure your GraphQL server accordingly -::: - -For example, you will have to setup any authentication and the realtime config: +```diff title="api/src/functions/graphql.ts" ++ import { realtime } from 'src/lib/realtime' -```ts - await fastify.register(redwoodFastifyGraphQLServer, { - // If authenticating, be sure to import and add in - // authDecoder, - // getCurrentUser, - loggerConfig: { - logger: logger, - options: { - query: true, - data: true, - operationName: true, - requestId: true, - }, - }, - graphiQLEndpoint: enableWeb ? '/.redwood/functions/graphql' : '/graphql', - sdls, - services, - directives, - allowIntrospection: true, - allowGraphiQL: true, - // Configure if using RedwoodJS Realtime - realtime, + export const handler = createGraphQLHandler({ + // ... ++ realtime, }) ``` -You can now remove the GraphQL handler function that resides in `api/functions/graphql.ts`. - ### Realtime Configuration -By default, RedwoodJS realtime configures an in-memory store for the Pub Sub client used with subscriptions and live query invalidation. +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: @@ -159,8 +131,7 @@ To enable defer and streaming, set `enableDeferStream` to true. Configure a Redis store and defer and stream in: -```ts -// api/lib/realtime.ts +```ts title="api/lib/realtime.ts" import { RedwoodRealtimeOptions } from '@redwoodjs/realtime' import subscriptions from 'src/subscriptions/**/*.{js,ts}' @@ -208,7 +179,7 @@ export const realtime: RedwoodRealtimeOptions = { #### PubSub and LiveQueryStore -By setting up RedwoodJS Realtime, the GraphQL server adds two helpers on the context: +By setting up realtime, the GraphQL server adds two helpers on the context: * pubSub * liveQueryStory @@ -229,7 +200,7 @@ When the query is: `auctions: [Auction!]! @requireAuth`: ## Subscriptions -RedwoodJS has a first-class developer experience for GraphQL subscriptions. +Redwood has a first-class developer experience for GraphQL subscriptions. #### Subscribe to Events @@ -264,7 +235,7 @@ This example showcases how a subscription yields its own response. ## Live Queries -RedwoodJS has made it super easy to add live queries to your GraphQL server! You can push new data to your clients automatically once the data selected by a GraphQL operation becomes stale by annotating your query operation with the `@live` directive. +Redwood has made it super easy to add live queries to your GraphQL server! You can push new data to your clients automatically once the data selected by a GraphQL operation becomes stale by annotating your query operation with the `@live` directive. The invalidation mechanism is based on GraphQL ID fields and schema coordinates. Once a query operation has been invalidated, the query is re-executed, and the result is pushed to the client. @@ -305,11 +276,11 @@ mutation MakeBid { ## Defer Directive -The `@defer` directive allows you to post-pone the delivery of one or more (slow) fields grouped in an inlined or spread fragment. +The `@defer` directive allows you to postpone the delivery of one or more (slow) fields grouped in an inlined or spread fragment. ### Slow and Fast Field Defer Example -Here, the GraphQL schema defines two queries for a "fast" and a "slow" (ie, delayed) information. +Here, the GraphQL schema defines two queries for a "fast" and a "slow" (i.e., delayed) information. ```graphql export const schema = gql` @@ -451,9 +422,7 @@ curl -g -X POST \ ``` Here you see the initial response has `[]` for alphabet data. - -Then on each push to the Repeater, an incremental update the the list of letters is sent. - +Then on each push to the Repeater, an incremental update to the list of letters is sent. The stream ends when `hasNext` is false: ```bash @@ -525,14 +494,14 @@ Content-Length: 17 ![image](https://github.com/ahaywood/redwoodjs-streaming-realtime-demos/assets/1051633/e3c51908-434c-4396-856a-8bee7329bcdd) -When deciding on how to offer realtime data updates in your RedwoodJS app, you’ll want to consider: +When deciding on how to offer realtime data updates, you’ll want to consider: - How frequently do your users require information updates? - Determine the value of "real-time" versus "near real-time" to your users. Do they need to know in less than 1-2 seconds, or is 10, 30, or 60 seconds acceptable for them to receive updates? - Consider the criticality of the data update. Is it low, such as a change in shipment status, or higher, such as a change in stock price for an investment app? - Consider the cost of maintaining connections and tracking updates across your user base. Is the infrastructure cost justifiable? - If you don't require "real" real-time, consider polling for data updates on a reasonable interval. According to Apollo, [in most cases](https://www.apollographql.com/docs/react/data/subscriptions/), your client should not use subscriptions to stay up to date with your backend. Instead, you should poll intermittently with queries or re-execute queries on demand when a user performs a relevant action, such as clicking a button. -- How are you deploying? Serverless or Serverful? +- How are you deploying? Serverless or serverful? - Real-time options depend on your deployment method. - If you are using a serverless architecture, your application cannot maintain a stateful connection to your users' applications. Therefore, it's not easy to "push," "publish," or "stream" data updates to the web client. - In this case, you may need to look for third-party solutions that manage the infrastructure to maintain such stateful connections to your web client, such as [Supabase Realtime](https://supabase.com/realtime), [SendBird](https://sendbird.com/), [Pusher](https://pusher.com/), or consider creating your own [AWS SNS-based](https://docs.aws.amazon.com/sns/latest/dg/welcome.html) functionality. @@ -541,7 +510,7 @@ When deciding on how to offer realtime data updates in your RedwoodJS app, you ## Showcase Demos -Please see our [showcase RedwoodJS Realtime app](https://realtime-demo.fly.dev) for exampes of subscriptions and live queries. It also demonstrates how you can handle streaming responses, like those used by OpenAI chat completions. +Please see our [showcase realtime app](https://realtime-demo.fly.dev) for examples of subscriptions and live queries. It also demonstrates how you can handle streaming responses, like those used by OpenAI chat completions. ### Chat Room (Subscription) diff --git a/docs/netlify.toml b/docs/netlify.toml index 63dafd244adf..2f07a9e47bc2 100644 --- a/docs/netlify.toml +++ b/docs/netlify.toml @@ -280,8 +280,19 @@ to = "/docs/3.x/:splat" status = 301 -[[plugins]] - package = "@algolia/netlify-plugin-crawler" +# Redirects for "Configuring Fastify" after the server file was released in v7 - [plugins.inputs] - branches = ['next'] +[[redirects]] + from = "/docs/app-configuration-redwood-toml#configure-fastify" + to = "/docs/docker#using-the-server-file" + status = 301 + +[[redirects]] + from = "/docs/app-configuration-redwood-toml#register-custom-fastify-plugins" + to = "/docs/docker#configuring-the-server" + status = 301 + +[[redirects]] + from = "/docs/app-configuration-redwood-toml#how-to-configure-fastify-to-accept-file-uploads" + to = "/docs/docker#configuring-the-server" + status = 301