diff --git a/docs/netlify.toml b/docs/netlify.toml index f38381adf5f9..45316c40a6df 100644 --- a/docs/netlify.toml +++ b/docs/netlify.toml @@ -213,3 +213,35 @@ from = "/dbauth" to = "/docs/authentication#self-hosted-auth-installation-and-setup" status = 301 + +# v1.0-v1.5 redirects (to v1.x) + +[[redirects]] + from = "/docs/1.0/*" + to = "/docs/1.x/:splat" + status = 301 + +[[redirects]] + from = "/docs/1.1/*" + to = "/docs/1.x/:splat" + status = 301 + +[[redirects]] + from = "/docs/1.2/*" + to = "/docs/1.x/:splat" + status = 301 + +[[redirects]] + from = "/docs/1.3/*" + to = "/docs/1.x/:splat" + status = 301 + +[[redirects]] + from = "/docs/1.4/*" + to = "/docs/1.x/:splat" + status = 301 + +[[redirects]] + from = "/docs/1.5/*" + to = "/docs/1.x/:splat" + status = 301 diff --git a/docs/versioned_docs/version-1.0/a11y.md b/docs/versioned_docs/version-1.0/a11y.md deleted file mode 100644 index 60a9e4b83ccf..000000000000 --- a/docs/versioned_docs/version-1.0/a11y.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -slug: accessibility ---- - -# Accessibility (aka a11y) - -We built Redwood to make building websites more accessible (we write all the config so you don't have to), but Redwood's also built to help you make more accessible websites. Accessibility shouldn't be a nice-to-have. It should be a given from the start, a core feature that's built-in and well-supported. - -There's a lot of great tooling out there that'll not only help you build accessible websites, but also help you learn exactly what that means. - -> **With all this tooling, do I still have to manually test my application?** -> -> Unequivocally, yes. Even with all the tooling in the world, manual testing's still important, especially for accessibility. -> The GDS Accessibility team found that [automated testing only catches ~30% of all the issues](https://accessibility.blog.gov.uk/2017/02/24/what-we-found-when-we-tested-tools-on-the-worlds-least-accessible-webpage). -> -> But just because the tools don't catch 'em all doesn't mean they're not valuable. It'd be much harder to learn what to look for without them. - -## Accessible Routing with Redwood Router - -For single-page applications (SPAs), accessibility starts with the Router. Without a full-page refresh, you just can't be sure that things like announcements and focus are being taken care of the way they're supposed to be. Here's a great example of [how disorienting SPAs can be to screen-reader users](https://www.youtube.com/watch?v=NKTdNv8JpuM). On navigation, nothing's announced. It's important not to understate the severity of this; the lack of an announcement isn't just buggy behavior, it's broken. - -Normally the onus would be on you as a developer to announce to screen-reader users that they've navigated somewhere new. That's a lot to ask, and hard to get right, especially when you're just trying to build your app. Luckily, if you're writing good content and marking it up semantically, there's nothing you have to do! Redwood automatically and always announces pages on navigation. Redwood looks for announcements in this order: - -1. `RouteAnnouncement` -2. `h1` -3. `document.title` -4. `location.pathname` - -The reason for this is that announcements should be as specific as possible; more specific usually means more descriptive, and more descriptive usually means that users can not only orient themselves and navigate through the content, but also find it again. -If you're not sure if your content is descriptive enough, see the [W3 guidelines](https://www.w3.org/WAI/WCAG21/Techniques/general/G88.html). - -Even though Redwood looks for a `RouteAnnouncement` first, you don't have to have one on every page—it's more than ok for the h1 to be what's announced. `RouteAnnouncement` is there for when the situation calls for a custom announcement. - -The API is simple: `RouteAnnouncement`'s children will be announced; note that this can be something on the page, or can be visually hidden using the `visuallyHidden` prop: - -```js -// web/src/pages/HomePage.js - -import { RouteAnnouncement } from '@redwoodjs/router' - -const HomePage = () => { - return ( - // this will still be visible - -

Welcome to my site!

-
- ) -} - -export default HomePage -``` - -```js -// web/src/pages/AboutPage.js - -import { RouteAnnouncement } from '@redwoodjs/router' - -const AboutPage = () => { - return ( -

Welcome to my site!

- // this won't be visible - - All about me - - ) -} - -export default AboutPage -``` - -Whenever possible, it's good to maintain parity between the visual and audible experience of your app. That's just to say that `visuallyHidden` shouldn't be the first thing you reach for. But it's there if you need it! - - - -## Focus - -On page change, Redwood Router resets focus to the top of the DOM so that users can navigate through the new page. While this is the expected behavior (and the behavior you usually want), for some apps, especially those with a lot of navigation, it can be cumbersome for users to have tab through all that nav before getting to the main point. (And that goes for every page change!) - -Right now, there's two ways to alleviate this in Redwood: with skip links and/or the `RouteFocus` component. - -### Skip links - -Since the main content isn't usually the first thing on the page, it's a good practice to provide a shortcut for keyboard and screen-reader users to skip to it. Skip links do just that, and if you generate a layout (`yarn rw g layout`) with the `--skipLink` flag, you'll get a layout with a skip link: - -```terminal -yarn rw g layout main --skipLink -``` - -```js -import { SkipNavLink, SkipNavContent } from '@redwoodjs/router' -import '@reach/skip-nav/styles.css' - -const MainLayout = ({ children }) => { - return ( - <> - - - -
{children}
- - ) -} - -export default MainLayout -``` - -`SkipNavLink` renders a link that remains hidden till focused; `SkipNavContent` renders a div as the target for the link. For more on these components, see the [Reach UI](https://reach.tech/skip-nav/#reach-skip-nav) docs. - -Making sure your navs have skip links is a great practice that goes a long way. And it really doesn't cost you much! -One thing you'll probably want to do is change the URL the skip link sends the user to when activated. You can do that by changing the `contentId` and `id` props of `SkipNavLink` and `SkipNavContent` respectively: - -```js - - -// ... - - -``` - -If you'd prefer to implement your own skip link, [Ben Myers' blog](https://benmyers.dev/blog/skip-links/) is a great resource, and a great place to read about accessibility in general. - -### RouteFocus - -Sometimes you don't want to just skip the nav, but send a user somewhere. In this situation, you of course have the foresight that that place is where the user wants to be! So please use this at your discretion—sending a user to an unexpected location can be worse than sending them back the top. - -Having said that, if you know that on a particular page change a user's focus is better off being directed to a particular element, the `RouteFocus` component is what you want: - -```js -import { RouteFocus } from '@redwoodjs/router' - -const ContactPage = () => ( - - - // the contact form the user actually wants to interact with - - - -) - -export default ContactPage -``` - -`RouteFocus` tells the router to send focus to it's child on page change. In the example above, when the user navigates to the contact page, the name text field on the form gets focus—the first field of the form they're here to fill out. - -For a video example of using `RouteFocus`, see our [meetup on Redwood's accessibility features](https://youtu.be/T1zs77LU68w?t=3240). diff --git a/docs/versioned_docs/version-1.0/app-configuration-redwood-toml.md b/docs/versioned_docs/version-1.0/app-configuration-redwood-toml.md deleted file mode 100644 index df076ad2f8f8..000000000000 --- a/docs/versioned_docs/version-1.0/app-configuration-redwood-toml.md +++ /dev/null @@ -1,269 +0,0 @@ -# App Configuration: redwood.toml - -You can configure your Redwood app's settings in `redwood.toml`. By default, `redwood.toml` lists the following configuration options: - -```toml -[web] - title = "Redwood App" - port = 8910 - apiUrl = "/.redwood/functions" - includeEnvironmentVariables = [] -[api] - port = 8911 -[browser] - open = true -``` - -These are listed by default because they're the ones that you're most likely to configure. But there are plenty more available. The rest are spread between Redwood's [webpack configuration files](https://github.com/redwoodjs/redwood/tree/main/packages/core/config) and `@redwoodjs/internal`'s [config.ts](https://github.com/redwoodjs/redwood/blob/main/packages/internal/src/config.ts#L70-L99): - -The options and their structure are based on Redwood's notion of sides and targets. Right now, Redwood has two fixed sides, API and Web, that target NodeJS Lambdas and Browsers respectively. In the future, we'll add support for more sides and targets, like Electron and React Native (you can already see them listed as enums in [TargetEnum](https://github.com/redwoodjs/redwood/blob/d51ade08118c17459cebcdb496197ea52485364a/packages/internal/src/config.ts#L11-L12)), and as we do, you'll see them reflected in `redwood.toml`. But right now, you'll most likely never touch options like `target`. - -The idea is that, in the future, changes here will have cascading, "app-level" effects. Using generators as an example, based on your side and target, the generators will behave differently, but appropriately different. - -> 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 convenience layer over Redwood's webpack configuration files. That is, for certain settings, instead of having to deal with webpack directly, we give you quick access via `redwood.toml`. Some of these settings are for development, some are for production, and some are for both. You can actually see this reflected in which webpack file each configuration option is referenced in—[webpack.development.js](https://github.com/redwoodjs/redwood/blob/main/packages/core/config/webpack.development.js), [webpack.production.js](https://github.com/redwoodjs/redwood/blob/main/packages/core/config/webpack.production.js), and [webpack.common.js](https://github.com/redwoodjs/redwood/blob/main/packages/core/config/webpack.common.js). - - - -`redwood.toml` also serves a slightly larger purpose: it's used to determine the base directory of a Redwood project. So this file is what really makes a Redwood app a Redwood app. If you remove it and run `yarn rw dev`, you'll get an error: - -```terminal -Error: Could not find a "redwood.toml" file, are you sure you're in a Redwood project? -``` - -(So don't do that!) - -## [web] - -Configuration for the web side. - -| Key | Description | Default | Context | -| :---------------------------- | :------------------------------------------------------------------------- | :---------------------- | :------------ | -| `title` | Title of your Redwood App | `'Redwood App'` | `both` | -| `host` | Hostname to listen on | `'localhost'` | `development` | -| `port` | Port to listen on | `8910` | `development` | -| `path` | Path to the web side | `'./web'` | `both` | -| `target` | Target for the web side | `'browser'` | `both` | -| `apiUrl` | Specify the URL to your api-server. Can be an absolute path or FQDN | `'/.redwood/functions'` | `production` | -| `apiGraphQLUrl` | Optional: URL or absolute path to GraphQL function, without trailing slash | `${apiUrl}/graphql` | `production` | -| `apiDbAuthUrl` | Optional: URL or absolute path to DbAuth function, without trailing slash | `${apiUrl}/auth` | `production` | -| `includeEnvironmentVariables` | Environment variables to whitelist | | `both` | -| `fastRefresh` | Enable webpack's fast refresh | true | `development` | -| `a11y` | Enable storybook `addon-a11y` and `eslint-plugin-jsx-a11y` | true | `development` | - -### API Paths - -You have full control over the path to your serverless functions via `apiUrl`. You can specify it either as a path (below) or a URL: - -```toml -[web] - apiUrl = "/.redwood/functions" -``` - -When you're running your app locally, this gets aliased away (you can see exactly how in [webpack.common.js](https://github.com/redwoodjs/redwood/blob/main/packages/core/config/webpack.development.js#L22) (and here's the docs on Webpack's [devServer.proxy](https://webpack.js.org/configuration/dev-server/#devserverproxy), for good measure)). - -When you run `redwood setup deploy [provider]` on the CLI, this path gets configured to the default for the provider you're configuring - -#### Customizing the GraphQL Endpoint - -By default, Redwood constructs 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. There's two ways you can do this: - -**a) Change `apiUrl` to your new domain** - -```toml -[web] - apiUrl = "https://api.coolredwoodapp.com" -``` - -This means your Redwood project's web side (i.e. the frontend) points to the above domain, and tries to access the GraphQL endpoint at `https://api.coolredwoodapp.com/graphql`. - -**b) Change only the graphql endpoint** - -You can also change the GraphQL endpoint only (without affecting other things, like dbAuth): - -```diff -[web] - apiUrl = "/.redwood/functions" -+ apiGraphqlEndpoint = "https://coolrwapp.mycdn.com" -``` - -This is particularly useful if you'd like to use a CDN provider like GraphCDN in front of your api side, independent of the web side. - -#### Customizing the DbAuth Endpoint - -If you're using dbAuth, you may decide to point your auth function (i.e. the serverless function used for login/signup) at a different host. To do this without affecting your GraphQL endpoint, you can add `apiDbAuthUrl` to your `redwood.toml`: - -```diff -[web] - apiUrl = "/.redwood/functions" -+ apiDbAuthUrl = "https://api.mycoolapp.com/auth" -``` - -> **Quick note**: if you point your web side to a different domain, please make sure you have [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) configured. Otherwise browser security features may block requests from the client. - -### includeEnvironmentVariables - - - - -```toml -[web] - includeEnvironmentVariables = ['API_KEY'] -``` - -Where `API_KEY` is defined in .env or .env.defaults: - -```plaintext -API_KEY=... -``` - -`includeEnvironmentVariables` is the set of environment variables to whitelist for the web side. You can also prefix environment variables with `REDWOOD_ENV_` (see [Environment Variables](environment-variables.md#web)). - -## [api] - -Configuration for the api side. - -| Key | Description | Default | Context | -|:---------------|:----------------------------------------------------|:---------------------------|:--------------| -| `host` | Hostname to listen on | `'localhost'` | `development` | -| `port` | Port to listen on | `8911` | `development` | -| `debugPort` | Port to expose for debugger to attach to during dev | `18911` | `development` | -| `path` | Path to the api side | `'./api'` | `both` | -| `serverConfig` | Path to the `server.config.js` file | `'./api/server.config.js'` | `both` | -| `target` | Target for the api side | `'node'` | `both` | - -### Server Configuration - -You can customize the Fastify Server settings used by the RedwoodJS dev server by defining `server.config.js` file and declaring where to find it using the `serverConfig` key. - -#### Example Server Configuration - -The following is an example `server.config.js` and what is the current default if a `serverConfig` is not specified. - -For the [Fastify Server options](https://www.fastify.io/docs/latest/Reference/Server/#factory) that you can set, see: https://www.fastify.io/docs/latest/Reference/Server/#factory. - -Examples include: logger settings, timeouts, maximum payload limits, and more. - -> **Note:** This configuration does not apply in a serverless deploy. - -```js -// ./api/server.config.js -/** - * This file allows you to configure the Fastify Server settings - * used by the RedwoodJS dev server. - * - * It also applies when running the api server with `yarn rw serve`. - * - * For the Fastify server options that you can set, see: - * https://www.fastify.io/docs/latest/Reference/Server/#factory - * - * Examples include: logger settings, timeouts, maximum payload limits, and more. - * - * Note: This configuration does not apply in a serverless deploy. - */ - -/** @type {import('fastify').FastifyServerOptions} */ -const config = { - requestTimeout: 15_000, - logger: { - level: process.env.NODE_ENV === 'development' ? 'debug' : 'warn', - }, -} -``` - -You may elect to configure different `server.config.js` based on your deployment environment and take advantage of [# Using Environment Variables in redwood.toml](#using-environment-variables-in-redwoodtoml). - -Given an environment variable `DEPLOY_ENVIRONMENT` that declares `development`, `staging`, `production`: - -``` -[api] - port = 8911 - serverConfig = "./api/${DEPLOY_ENVIRONMENT}-server.config.js" -``` - -## [browser] - -Configuration for the browser target. - -| Key | Description | Default | Context | -| :----- | :---------------------------------------------------------------- | :------ | :------------ | -| `open` | Open the browser to web's `host:port` after the dev server starts | `false` | `development` | - -### open - -```toml -[browser] - open = true -``` - -Setting `open` to `true` like this will open the browser to web's `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. Or just remove it entirely. - -You can also provide the name of a browser to use instead of the system default. E.g., `open = 'Firefox'` will open Firefox regardless of which browser's the default on your system. - -> When you generate a new app, the `open` value is set to `true`. If you delete the `open` config from `redwood.toml`, it will default to `false`. For example, removing the line `open = true` disables automatic browser opening. - -There's a lot more you can do here. For all the details, see Webpack's docs on [devServer.open](https://webpack.js.org/configuration/dev-server/#devserveropen). - -## [generate] - -```toml -[generate] - tests = true - stories = true -``` - -Configuration for Generator "test" and "story" files. By default, the following Generators create Jest test and/or Storybook files (with mock data files when applicable) along with specific component file(s): component, cell, layout, page, sdl, and services. Understandably, this is a lot of files, and sometimes you don't want all of them, either because you don't plan on using Jest/Storybook, or are just getting started and don't want the overhead. These toml keys allows you to toggle the generation of test and story files on and off. - -| Key | Description | Default | -| :-------- | :----------------------------- | :------ | -| `tests` | Generate Jest test files | `true` | -| `stories` | Generate Storybook story files | `true` | - -### Tests - -Setting to `true` creates tests when the generate command is invoked. - -### Stories - -Setting to `true` creates stories for [Storybook](https://storybook.js.org/) when the generate command is invoked. - -## Running within a Container or VM - -To run a Redwood app within a container or VM, you'll want to set both the web and api's `host` to `0.0.0.0` to allow network connections to and from the host: - -```toml -[web] - host = '0.0.0.0' - ... -[api] - host = '0.0.0.0' - ... -``` - -## Using Environment Variables in `redwood.toml` - -Sometimes you want to change your `redwood.toml` based on the environment you're deploying to. -For example, you may want to point to a different `apiUrl` in your staging environment. -You can do this with environment variables. -Let's look at an example: - -```toml -[web] - title = "App running on ${APP_TITLE}" - port = "${PORT:8910}" - apiUrl = "${API_URL:/.redwood/functions}" - includeEnvironmentVariables = [] -``` - -This does the following: - -- sets `title` by interpolating the env var `APP_TITLE` -- sets `port` to the env var `PORT`, falling back to `8910` -- sets `apiUrl` to the env var `API_URL`, falling back to `/.redwood/functions` (the default) - -That's pretty much all there is to it. Just remember two things: - -1. fallbacks are always strings -2. these values are interpolated at _build_ time diff --git a/docs/versioned_docs/version-1.0/assets-and-files.md b/docs/versioned_docs/version-1.0/assets-and-files.md deleted file mode 100644 index 72e6040a834e..000000000000 --- a/docs/versioned_docs/version-1.0/assets-and-files.md +++ /dev/null @@ -1,70 +0,0 @@ -# Assets and Files - -> ⚠ **Work in Progress** ⚠️ -> -> There's more to document here. In the meantime, you can check our [community forum](https://community.redwoodjs.com/search?q=assets%20and%20files) for answers. -> -> Want to contribute? Redwood welcomes contributions and loves helping people become contributors. -> You can edit this doc [here](https://github.com/redwoodjs/redwoodjs.com/blob/main/docs/assetsAndFiles.md). -> If you have any questions, just ask for help! We're active on the [forums](https://community.redwoodjs.com/c/contributing/9) and on [discord](https://discord.com/channels/679514959968993311/747258086569541703). - -There are two methods for adding assets to a Redwood app: - -i) Webpack imports and -ii) directly adding to the `/public` folder. - -## Importing Assets - -In general, it's best to import files directly into a template, page or component. This allows Webpack to include that file in the bundle, ensuring correct processing for the distribution folder while providing error checks and correct paths along the way. - -### Example Asset Import with Webpack - -Using `import`, we can do the following: - -```javascript -import React from 'react' -import logo from './my-logo.jpg' - -function Header() { - return Logo -} - -export default Header -``` - -Webpack will correctly handle the file path and add the file to the distribution folder within `/dist/media` (created when Webpack builds for production). - -> Note: In this example, the file `my-logo.jpg` is located in the same directory as the component. This is recommended practice to keep all files related to a component in a single directory. - -Behind the scenes, we are using Webpack's ["file-loader"](https://webpack.js.org/loaders/file-loader/) and ["url-loader"](https://webpack.js.org/loaders/url-loader/) (which transforms images less than 10kb into data URIs for improved performance). - -## Directly Adding Assets using the "Public" Folder - -Alternately, you can add files directly to the folder "web/public", effectively adding static files to your app. All included files and folders will be copied into the production build `web/dist` folder. They will also be available during development when you run `yarn rw dev`. - -Because assets in this folder are bypassing the javascript module system, **this folder should be used sparingly** for assets such as favicons, robots.txt, manifests, libraries incompatible with Webpack, etc. - -> Note: files will _not_ hot reload while the development server is running. You'll need to manually stop/start to access file changes. - -Behind the scenes, Redwood is using Webpack's ["copy-webpack-plugin"](https://github.com/webpack-contrib/copy-webpack-plugin). - -### Example Use - -Assuming `public/` includes the following: - -- `favicon.png` -- `static-files/my-logo.jpg` - -Running `yarn build` will copy the file `favicon.png` to `/dist/favicon.png`. The new directory with file `static-files/my-logo.jpg` will be copied to `/dist/static-files/my-logo.jpg`. These can be referenced in your code directly without any special handling, e.g. - -```html - -``` - -and - -```html -Logo -``` - -> Note: because the directory `dist/` becomes your production root, it should not be included in the path. diff --git a/docs/versioned_docs/version-1.0/authentication.md b/docs/versioned_docs/version-1.0/authentication.md deleted file mode 100644 index 3920e63f3dc9..000000000000 --- a/docs/versioned_docs/version-1.0/authentication.md +++ /dev/null @@ -1,1496 +0,0 @@ -# Authentication - -`@redwoodjs/auth` contains both a built-in database-backed authentication system (dbAuth), as well as lightweight wrappers around popular SPA authentication libraries. - -We currently support the following third-party authentication providers: - -- [Netlify Identity Widget](https://github.com/netlify/netlify-identity-widget) -- [Auth0](https://github.com/auth0/auth0-spa-js) -- [Azure Active Directory](https://github.com/AzureAD/microsoft-authentication-library-for-js) -- [Clerk](https://clerk.dev) -- [Netlify GoTrue-JS](https://github.com/netlify/gotrue-js) -- [Magic Links - Magic.js](https://github.com/MagicHQ/magic-js) -- [Firebase](https://firebase.google.com/docs/auth) -- [Ethereum](https://github.com/oneclickdapp/ethereum-auth) -- [Supabase](https://supabase.io/docs/guides/auth) -- [Nhost](https://docs.nhost.io/platform/authentication) -- Custom -- [Contribute one](https://github.com/redwoodjs/redwood/tree/main/packages/auth), it's SuperEasy™! - -Check out the [Auth Playground](https://github.com/redwoodjs/playground-auth). - -## Self-hosted Auth Installation and Setup - -Redwood's own **dbAuth** provides several benefits: - -- Use your own database for storing user credentials -- Use your own login, signup and forgot password pages (or use Redwood's pre-built ones) -- Customize login session length -- No external dependencies -- No user data ever leaves your servers -- No additional charges/limits based on number of users -- No third party service outages affecting your site - -And potentially one large drawback: - -- Use your own database for storing user credentials - -However, we're following best practices for storing these credentials: - -1. Users' passwords are [salted and hashed](https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/) with PBKDF2 before being stored -2. Plaintext passwords are never stored anywhere, and only transferred between client and server during the login/signup phase (and hopefully only over HTTPS) -3. Our logger scrubs sensitive parameters (like `password`) before they are output - -Even if you later decide you want to let someone else handle your user data for you, dbAuth is a great option for getting up and running quickly (we even have a generator for creating basic login and signup pages for you). - -### How It Works - -dbAuth relies on good ol' fashioned cookies to determine whether a user is logged in or not. On an attempted login, a serverless function on the api-side checks whether a user exists with the given username (internally, dbAuth refers to this field as _username_ but you can use anything you want, like an email address). If a user with that username is found, does their salted and hashed password match the one in the database? - -If so, an [HttpOnly](https://owasp.org/www-community/HttpOnly), [Secure](https://owasp.org/www-community/controls/SecureCookieAttribute), [SameSite](https://owasp.org/www-community/SameSite) cookie (dbAuth calls this the "session cookie") is sent back to the browser containing the ID of the user. The content of the cookie is a simple string, but AES encrypted with a secret key (more on that later). - -When the user makes a GraphQL call, we decrypt the cookie and make sure that the user ID contained within still exists in the database. If so, the request is allowed to proceed. - -If there are any shenanigans detected (the cookie can't be decrypted properly, or the user ID found in the cookie does not exist in the database) the user is immediately logged out by expiring the session cookie. - -### Setup - -A single CLI command will get you everything you need to get dbAuth working, minus the actual login/signup pages: - - yarn rw setup auth dbAuth - -Read the post-install instructions carefully as they contain instructions for adding database fields for the hashed password and salt, as well as how to configure the auth serverless function based on the name of the table that stores your user data. Here they are, but could change in future releases: - -> You will need to add a couple of fields to your User table in order to store a hashed password and salt: -> -> model User { -> id Int @id @default(autoincrement()) -> email String @unique -> hashedPassword String // <─┐ -> salt String // <─┼─ add these lines -> resetToken String? // <─┤ -> resetTokenExpiresAt DateTime? // <─┘ -> } -> -> If you already have existing user records you will need to provide a default value or Prisma complains, so change those to: -> -> hashedPassword String @default("") -> salt String @default("") -> -> You'll need to let Redwood know what field you're using for your users' `id` and `username` fields In this case we're using `id` and `email`, so update those in the `authFields` config in `/api/src/functions/auth.js` (this is also the place to tell Redwood if you used a different name for the `hashedPassword` or `salt` fields): -> -> authFields: { -> id: 'id', -> username: 'email', -> hashedPassword: 'hashedPassword', -> salt: 'salt', -> resetToken: 'resetToken', -> resetTokenExpiresAt: 'resetTokenExpiresAt', -> }, -> -> To get the actual user that's logged in, take a look at `getCurrentUser()` in `/api/src/lib/auth.js`. We default it to something simple, but you may use different names for your model or unique ID fields, in which case you need to update those calls (instructions are in the comment above the code). -> -> Finally, we created a `SESSION_SECRET` environment variable for you in `.env`. This value should NOT be checked into version control and should be unique for each environment you deploy to. If you ever need to log everyone out of your app at once change this secret to a new value. To create a new secret, run: -> -> yarn rw g secret -> -> Need simple Login, Signup and Forgot Password pages? Of course we have a generator for those: -> -> yarn rw generate dbAuth - -Note that if you change the fields named `hashedPassword` and `salt`, and you have some verbose logging in your app, you'll want to scrub those fields from appearing in your logs. See the [Redaction](logger.md#redaction) docs for info. - -### Scaffolding Login/Signup/Forgot Password Pages - -If you don't want to create your own login, signup and forgot password pages from scratch we've got a generator for that: - - yarn rw g dbAuth - -The default routes will make them available at `/login`, `/signup`, `/forgot-password`, and `/reset-password` but that's easy enough to change. Again, check the post-install instructions for one change you need to make to those pages: where to redirect the user to once their login/signup is successful. - -If you'd rather create your own, you might want to start from the generated pages anyway as they'll contain the other code you need to actually submit the login credentials or signup fields to the server for processing. - -### Configuration - -Almost all config for dbAuth lives in `api/src/functions/auth.js` in the object you give to the `DbAuthHandler` initialization. The comments above each key will explain what goes where. Here's an overview of the more important options: - -#### login.handler() - -If you want to do something other than immediately let a user log in if their username/password is correct, you can add additional logic in `login.handler()`. For example, if a user's credentials are correct, but they haven't verified their email address yet, you can throw an error in this function with the appropriate message and then display it to the user. If the login should proceed, simply return the user that was passed as the only argument to the function: - -```javascript -login: { - handler: (user) => { - if (!user.verified) { - throw new Error('Please validate your email first!') - } else { - return user - } - } -} -``` - -#### signup.handler() - -This function should contain the code needed to actually create a user in your database. You will receive a single argument which is an object with all of the fields necessary to create the user (`username`, `hashedPassword` and `salt`) as well as any additional fields you included in your signup form in an object called `userAttributes`: - -```javascript -signup: { - handler: ({ username, hashedPassword, salt, userAttributes }) => { - return db.user.create({ - data: { - email: username, - hashedPassword: hashedPassword, - salt: salt, - name: userAttributes.name, - }, - }) - } -} -``` - -Before `signup.handler()` is invoked, dbAuth will check that the username is unique in the database and throw an error if not. - -There are three things you can do within this function depending on how you want the signup to proceed: - -1. If everything is good and the user should be logged in after signup: return the user you just created -2. If the user is safe to create, but you do not want to log them in automatically: return a string, which will be returned by the `signUp()` function you called after destructuring it from `useAuth()` (see code snippet below) -3. If the user should _not_ be able to sign up for whatever reason: throw an error in this function with the message to be displayed - -You can deal with case #2 by doing something like the following in a signup component/page: - -```javascript -const { signUp } = useAuth() - -const onSubmit = async (data) => { - const response = await signUp({ ...data }) - - if (response.message) { - toast.error(response.message) // user created, but not logged in - } else { - toast.success('Welcome!') // user created and logged in - navigate(routes.dashboard()) - } -} -``` - -#### forgotPassword.handler() - -This handler is invoked if a user is found with the username/email that they submitted on the Forgot Password page, and that user will be passed as an argument. Inside this function is where you'll send the user a link to reset their password—via an email is most common. The link will, by default, look like: - - https://example.com/reset-password?resetToken=${user.resetToken} - -If you changed the path to the Reset Password page in your routes you'll need to change it here. If you used another name for the `resetToken` database field, you'll need to change that here as well: - - https://example.com/reset-password?resetKey=${user.resetKey} - -#### resetPassword.handler() - -This handler is invoked after the password has been successfully changed in the database. Returning something truthy (like `return user`) will automatically log the user in after their password is changed. If you'd like to return them to the login page and make them log in manually, `return false` and redirect the user in the Reset Password page. - -#### Cookie config - -These options determine how the cookie that tracks whether the client is authorized is stored in the browser. The default configuration should work for most use cases. If you serve your web and api sides from different domains you'll need to make some changes: set `SameSite` to `None` and then add [CORS configuration](#cors-config). - -```javascript -cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: true, - // Domain: 'example.com', -} -``` - -#### CORS config - -If you're using dbAuth and your api and web sides are deployed to different domains then you'll need to configure CORS for both GraphQL in general and dbAuth. You'll also need to enable a couple of options to be sure and send/accept credentials in XHR requests. For more info, see the complete [CORS doc](cors.md#cors-and-authentication). - -#### Error Messages - -There are several error messages that can be displayed, including: - -- Username/email not found -- Incorrect password -- Expired reset password token - -We've got some default error messages that sound nice, but may not fit the tone of your site. You can customize these error messages in `api/src/functions/auth.js` in the `errors` prop of each of the `login`, `signup`, `forgotPassword` and `resetPassword` config objects. The generated file contains tons of comments explaining when each particular error message may be shown. - -### Environment Variables - -#### Cookie Domain - -By default, the session cookie will not have the `Domain` property set, which a browser will default to be the [current domain only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent). If your site is spread across multiple domains (for example, your site is at `example.com` but your api-side is deployed to `api.example.com`) you'll need to explicitly set a Domain so that the cookie is accessible to both. - -To do this, create an environment variable named `DBAUTH_COOKIE_DOMAIN` set to the root domain of your site, which will allow it to be read by all subdomains as well. For example: - - DBAUTH_COOKIE_DOMAIN=example.com - -#### Session Secret Key - -If you need to change the secret key that's used to encrypt the session cookie, or deploy to a new target (each deploy environment should have its own unique secret key) we've got a CLI tool for creating a new one: - - yarn rw g secret - -Note that the secret that's output is _not_ appended to your `.env` file or anything else, it's merely output to the screen. You'll need to put it in the right place after that. - -> The `.env` file is set to be ignored by git and not committed to version control. There is another file, `.env.defaults`, which is meant to be safe to commit and contain simple ENV vars that your dev team can share. The encryption key for the session cookie is NOT one of these shareable vars! - -## Third Party Providers Installation and Setup - -You will need to instantiate your authentication client and pass it to the ``. See instructions below for your specific provider. - -### Netlify Identity Widget - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth netlify -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth netlify-identity-widget -``` - -#### Setup - -You will need to enable Identity on your Netlify site. - - -```js -// web/src/App.js -import { AuthProvider } from '@redwoodjs/auth' -import netlifyIdentity from 'netlify-identity-widget' -import { isBrowser } from '@redwoodjs/prerender/browserUtils' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -isBrowser && netlifyIdentity.init() - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Netlify Identity Auth Provider Specific Setup - -See the Netlify Identity information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. - -+++ - -### GoTrue-JS - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth goTrue -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth gotrue-js -``` - -#### Setup - -You will need to enable Identity on your Netlify site. - - -Add the GoTrue-JS package to the web side: - -```terminal -yarn workspace web add gotrue-js -``` - -Instantiate GoTrue and pass in your configuration. Be sure to set APIUrl to the API endpoint found in your Netlify site's Identity tab: - -```js -// web/src/App.js -import { AuthProvider } from '@redwoodjs/auth' -import GoTrue from 'gotrue-js' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const goTrueClient = new GoTrue({ - APIUrl: 'https://MYAPP.netlify.app/.netlify/identity', - setCookie: true, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -+++ - -### Auth0 - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth auth0 -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth @auth0/auth0-spa-js -``` - -#### Setup - -To get your application keys, only complete the ["Configure Auth0"](https://auth0.com/docs/quickstart/spa/react#get-your-application-keys) section of the SPA Quickstart guide. - -**NOTE** If you're using Auth0 with Redwood then you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of the required JWT token. - -The `useRefreshTokens` options is required for automatically extending sessions beyond that set in the initial JWT expiration (often 3600/1 hour or 86400/1 day). - -If you want to allow users to get refresh tokens while offline, you must also enable the Allow Offline Access switch in your Auth0 API Settings as part of setup configuration. See: [https://auth0.com/docs/tokens/refresh-tokens](https://auth0.com/docs/tokens/refresh-tokens) - -You can increase security by using refresh token rotation which issues a new refresh token and invalidates the predecessor token with each request made to Auth0 for a new access token. - -Rotating the refresh token reduces the risk of a compromised refresh token. For more information, see: [https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation](https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation). - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to include them in your `redwood.toml`. - -```js -// web/src/App.js -import { AuthProvider } from '@redwoodjs/auth' -import { Auth0Client } from '@auth0/auth0-spa-js' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const auth0 = new Auth0Client({ - domain: process.env.AUTH0_DOMAIN, - client_id: process.env.AUTH0_CLIENT_ID, - redirect_uri: process.env.AUTH0_REDIRECT_URI, - - // ** NOTE ** Storing tokens in browser local storage provides persistence across page refreshes and browser tabs. - // However, if an attacker can achieve running JavaScript in the SPA using a cross-site scripting (XSS) attack, - // they can retrieve the tokens stored in local storage. - // https://auth0.com/docs/libraries/auth0-spa-js#change-storage-options - cacheLocation: 'localstorage', - audience: process.env.AUTH0_AUDIENCE, - - // @MARK: useRefreshTokens is required for automatically extending sessions - // beyond that set in the initial JWT expiration. - // - // @MARK: https://auth0.com/docs/tokens/refresh-tokens - // useRefreshTokens: true, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Login and Logout Options - -When using the Auth0 client, `login` and `logout` take `options` that can be used to override the client config: - -- `returnTo`: a permitted logout url set in Auth0 -- `redirectTo`: a target url after login - -The latter is helpful when an unauthenticated user visits a Private route, but then is redirected to the `unauthenticated` route. The Redwood router will place the previous requested path in the pathname as a `redirectTo` parameter which can be extracted and set in the Auth0 `appState`. That way, after successfully logging in, the user will be directed to this `targetUrl` rather than the config's callback. - -```js -const UserAuthTools = () => { - const { loading, isAuthenticated, logIn, logOut } = useAuth() - - if (loading) { - // auth is rehydrating - return null - } - - return ( - - ) -} -``` - -#### Auth0 Auth Provider Specific Setup - -See the Auth0 information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. - -+++ - -### Clerk - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth clerk -``` - -#### Setup - -To get started with Clerk, sign up on [their website](https://clerk.dev/) and create an application, or follow their [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) that has an [example repo](https://github.com/redwoodjs/redwood-tutorial) already setup. - -It's important that the `ClerkAuthProvider` added to your `App.{js|ts}` file during setup is within the `RedwoodProvider` and around Redwood's `AuthProvider`: - -```ts{6,12} -// web/src/App.{js|ts} - -const App = () => ( - - - - - - - - - - - -) -``` - -The [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) also explains how to use `@clerk/clerk-react` components with Redwood's `useAuth()` hook: - -```ts -import { UserButton, SignInButton } from '@clerk/clerk-react' - -// ... - -{ - isAuthenticated ? ( - - ) : ( - - - - ) -} -``` - -Applications in Clerk have different instances. By default, there's one for development, one for staging, and one for production. You'll need to pull three values from one of these instances. We recommend storing the development values in your local `.env` file and using the staging and production values in the appropriate env setups for your hosting platform when you deploy. - -The three values you'll need from Clerk are your instance's "Frontend API Key" url, a "Backend API key" and a "JWT verification key", all from your instance's settings under "API Keys". The Frontend API url should be stored in an env variable named `CLERK_FRONTEND_API_URL`. The Backend API key should be named `CLERK_API_KEY`. Finally, the JWT key should be named `CLERK_JWT_KEY` - -Otherwise, feel free to configure your instances however you wish with regards to their appearance and functionality. - -> **Including Environment Variables in Serverless Deploys** -> -> In addition to adding these env vars to your local `.env` file or deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given above, follow the instructions in [this document](environment-variables.md). You need to expose the `CLERK_FRONTEND_API_URL` variable to the `web` side. - -#### Login and Logout Options - -When using the Clerk client, `login` and `signUp` take an `options` object that can be used to override the client config. - -For `login` the `options` may contain all the options listed at the Clerk [props documentation for login](https://docs.clerk.dev/reference/clerkjs/clerk#signinprops). - -For `signUp` the `options` may contain all the options listed at the Clerk [props documentation for signup](https://docs.clerk.dev/reference/clerkjs/clerk#signupprops). - -#### Avoiding Feature Duplication Confusion - -Redwood's integration of Clerk is based on [Clerk's React SDK](https://docs.clerk.dev/reference/clerk-react). This means there is some duplication between the features available through that SDK and the ones available in the `@redwoodjs/auth` package - such as the alternatives of using Clerk's `SignedOut` component to redirect users away from a private page vs. using Redwood's `Private` route wrapper. In general, we would recommend you use the **Redwood** way of doing things when possible, as that is more likely to function harmoniously with the rest of Redwood. That being said, though, there are some great features in Clerk's SDK that you will be able to now use in your app, such as the `UserButton` and `UserProfile` components. - -+++ - -### Azure Active Directory - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth azureActiveDirectory -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @azure/msal-browser -``` - -#### Setup - -To get your application credentials, create an [App Registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration) using in your Azure Active Directory tenant and make sure you configure as a [MSAL.js 2.0 with auth code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration#redirect-uri-msaljs-20-with-auth-code-flow) registration. Take a note of your generated _Application ID_ (client), and the _Directory ID_ (tenant). - -[Learn more about authorization code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-third-party-cookies-spas). - -##### Redirect URIs - -Enter allowed redirect urls for the integrations, e.g. `http://localhost:8910/login`. This will be the `AZURE_ACTIVE_DIRECTORY_REDIRECT_URI` environment variable, and suggestively `AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI`. - -#### Authority - -The Authority is a URL that indicates a directory that MSAL can request tokens from which you can read about [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority). However, you most likely want to have e.g. `https://login.microsoftonline.com/` as Authority URL, where `` is the Azure Active Directory tenant id. This will be the `AZURE_ACTIVE_DIRECTORY_AUTHORITY` environment variable. - -```js -// web/src/App.js -import { AuthProvider } from '@redwoodjs/auth' -import { PublicClientApplication } from '@azure/msal-browser' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const azureActiveDirectoryClient = new PublicClientApplication({ - auth: { - clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID, - authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY, - redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI, - postLogoutRedirectUri: process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI, - }, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Roles - -To setup your App Registration with custom roles and have them exposed via the `roles` claim, follow [this documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps). - -#### Login Options - -Options in method `logIn(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest) and is a good place to pass in optional [scopes](https://docs.microsoft.com/en-us/graph/permissions-reference#user-permissions) to be authorized. By default, MSAL sets `scopes` to [/.default](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope) which is built in for every application that refers to the static list of permissions configured on the application registration. Furthermore, MSAL will add `openid` and `profile` to all requests. In example below we explicit include `User.Read.All` to the login scope. - -```js -await logIn({ - scopes: ['User.Read.All'], // becomes ['openid', 'profile', 'User.Read.All'] -}) -``` - -See [loginRedirect](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#loginredirect), [PublicClientApplication](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html) class and [Scopes Behavior](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/docs/scopes.md#scopes-behavior) for more documentation. - -#### getToken Options - -Options in method `getToken(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest). By default, `getToken` will be called with scope `['openid', 'profile']`. As Azure Active Directory apply [incremental consent](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#dynamic-scopes-and-incremental-consent), we can extend the permissions from the login example by including another scope, for example `Mail.Read`. - -```js -await getToken({ - scopes: ['Mail.Read'], // becomes ['openid', 'profile', 'User.Read.All', 'Mail.Read'] -}) -``` - -See [acquireTokenSilent](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#acquiretokensilent), [Resources and Scopes](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#resources-and-scopes) or [full class documentation](https://pub.dev/documentation/msal_js/latest/msal_js/PublicClientApplication-class.html#constructors) for more documentation. - -+++ - -### Magic.Link - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth magicLink -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth magic-sdk -``` - -#### Setup - -To get your application keys, go to [dashboard.magic.link](https://dashboard.magic.link/) then navigate to the API keys add them to your `.env`. - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to "Whitelist them in your `redwood.toml`". - -```js -// web/src/App.js|tsx -import { useAuth, AuthProvider } from '@redwoodjs/auth' -import { Magic } from 'magic-sdk' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const m = new Magic(process.env.MAGICLINK_PUBLIC) - -const App = () => ( - - - - - - - -) - -export default App -``` - -```js -// web/src/Routes.js|tsx -import { useAuth } from '@redwoodjs/auth' -import { Router, Route } from '@redwoodjs/router' - -const Routes = () => { - return ( - - - - - ) -} - -export default Routes -``` - -#### Magic.Link Auth Provider Specific Integration - -See the Magic.Link information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. -+++ - -### Firebase - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth firebase -``` - -#### Setup - -We're using [Firebase Google Sign-In](https://firebase.google.com/docs/auth/web/google-signin), so you'll have to follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin#before_you_begin) steps in this guide. **Only** follow the "Before you begin" parts. - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](https://redwoodjs.com/docs/environment-variables) to "Whitelist them in your `redwood.toml`". - -```js -// web/src/App.js -import { AuthProvider } from '@redwoodjs/auth' -import { initializeApp, getApps, getApp } from '@firebase/app' -import * as firebaseAuth from '@firebase/auth' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const firebaseClientConfig = { - apiKey: process.env.FIREBASE_API_KEY, - authDomain: process.env.FIREBASE_AUTH_DOMAIN, - databaseURL: process.env.FIREBASE_DATABASE_URL, - projectId: process.env.FIREBASE_PROJECT_ID, - storageBucket: process.env.FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.FIREBASE_APP_ID, -} - -const firebaseApp = ((config) => { - const apps = getApps() - if (!apps.length) { - initializeApp(config) - } - return getApp() -})(firebaseConfig) - -export const firebaseClient = { - firebaseAuth, - firebaseApp, -} - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Usage - -```js -const UserAuthTools = () => { - const { loading, isAuthenticated, logIn, logOut } = useAuth() - - if (loading) { - return null - } - - return ( - - ) -} -``` - -#### Firebase Auth Provider Specific Integration - -See the Firebase information within this doc's [Auth Provider Specific Integration](https://redwoodjs.com/docs/authentication.html#auth-provider-specific-integration) section. -+++ - -### Supabase - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth supabase -``` - -#### Setup - -Update your .env file with the following settings supplied when you created your new Supabase project: - -- `SUPABASE_URL` with the unique Supabase URL for your project -- `SUPABASE_KEY` with the unique Supabase Key that identifies which API KEY to use -- `SUPABASE_JWT_SECRET` with the secret used to sign and verify the JSON Web Token (JWT) - -You can find these values in your project's dashboard under Settings -> API. - -For full Supabase documentation, see: - -#### Usage - -Supabase supports several sign in methods: - -- email/password -- passwordless via emailed magiclink -- authenticate via phone with SMS based OTP (One-Time Password) tokens. See: [SMS OTP with Twilio](https://supabase.io/docs/guides/auth/auth-twilio) -- Sign in with redirect. You can control where the user is redirected to after they are logged in via a `redirectTo` option. -- Sign in with a valid refresh token that was returned on login. -- Sign in using third-party providers/OAuth via - - [Apple](https://supabase.io/docs/guides/auth/auth-apple) - - Azure Active Directory - - [Bitbucket](https://supabase.io/docs/guides/auth/auth-bitbucket) - - [Discord](https://supabase.io/docs/guides/auth/auth-discord) - - [Facebook](https://supabase.io/docs/guides/auth/auth-facebook) - - [GitHub](https://supabase.io/docs/guides/auth/auth-github) - - [GitLab](https://supabase.io/docs/guides/auth/auth-gitlab) - - [Google](https://supabase.io/docs/guides/auth/auth-google) - - [Twitch](https://supabase.io/docs/guides/auth/auth-twitch) - - [Twitter](https://supabase.io/docs/guides/auth/auth-twitter) -- Sign in with a [valid refresh token](https://supabase.io/docs/reference/javascript/auth-signin#sign-in-using-a-refresh-token-eg-in-react-native) that was returned on login. Used e.g. in React Native. -- Sign in with scopes. If you need additional data from an OAuth provider, you can include a space-separated list of `scopes` in your request options to get back an OAuth `provider_token`. - -Depending on the credentials provided: - -- A user can sign up either via email or sign in with supported OAuth provider: `'apple' | 'azure' | 'bitbucket' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'twitch' | 'twitter'` -- If you sign in with a valid refreshToken, the current user will be updated -- If you provide email without a password, the user will be sent a magic link. -- The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on `app.supabase.io` for your project. -- Specifying an OAuth provider will open the browser to the relevant login page -- Note: You must enable and configure the OAuth provider appropriately. To configure these providers, you can go to Authentication -> Settings on `app.supabase.io` for your project. -- Note: To authenticate using SMS based OTP (One-Time Password) you will need a [Twilio](https://www.twilio.com/try-twilio) account - -For Supabase Authentication documentation, see: - -+++ - -### Ethereum - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth ethereum -``` - -#### Setup - -To complete setup, you'll also need to update your `api` server manually. See https://github.com/oneclickdapp/ethereum-auth for instructions. - -+++ - -### Nhost - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth nhost -``` - -#### Setup - -Update your .env file with the following setting which can be found on your Nhost project's dashboard. - -- `NHOST_BACKEND_URL` with the unique Nhost Backend URL that can be found in the app's dashboard. -- `NHOST_JWT_SECRET` with the JWT Key secret that you have set in your project's Settings > Hasura "JWT Key" section. - -#### Usage - -Nhost supports the following methods: - -- email/password -- passwordless with email -- passwordless with SMS -- OAuth Providers (via GitHub, Google, Facebook, Spotify, Discord, Twitch, Apple, Twitter, Microsoft and Linkedin). - -Depending on the credentials provided: - -- A user can sign in either via email or a supported OAuth provider. -- A user can sign up via email and password. For OAuth simply sign in and the user account will be created if it does not exist. -- Note: You must enable and configure the OAuth provider appropriately. To enable and configure a provider, please navigate to Users -> Login settings, from your app's dashboard. - -For the docs on Authentication, see: - -If you are also **using Nhost as your GraphQL API server**, you will need to pass `skipFetchCurrentUser` as a prop to `AuthProvider` , as follows: - -```js - -``` - -This avoids having an additional request to fetch the current user which is meant to work with Apollo Server and Prisma. - -Important: The `skipFetchCurrentUser` attribute is **only** needed if you are **not** using the standard RedwoodJS api side GraphQL Server. -+++ - -### Custom - -+++ View Installation and Setup - -#### Installation - -The following CLI command (not implemented, see https://github.com/redwoodjs/redwood/issues/1585) will install required packages and generate boilerplate code and files for Redwood Projects: - -```terminal -yarn rw setup auth custom -``` - -#### Setup - -It is possible to implement a custom provider for Redwood Auth. In which case you might also consider adding the provider to Redwood itself. - -If you are trying to implement your own auth, support is very early and limited at this time. Additionally, there are many considerations and responsibilities when it comes to managing custom auth. For most cases we recommend using an existing provider. - -However, there are examples contributed by developers in the Redwood forums and Discord server. - -The most complete example (although now a bit outdated) is found in [this forum thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610). Here's another [helpful message in the thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610/25). -+++ - -## API - -The following values are available from the `useAuth` hook: - -- async `logIn(options?)`: Differs based on the client library, with Netlify Identity a pop-up is shown, and with Auth0 the user is redirected. Options are passed to the client. -- async `logOut(options?)`: Log the current user out. Options are passed to the client. -- async `signUp(options?)`: If the provider has a sign up flow we'll show that, otherwise we'll fall back to the logIn flow. -- `currentUser`: An object containing information about the current user as set on the `api` side, or `null` if the user is not authenticated. -- `userMetadata`: An object containing the user's metadata (or profile information) fetched directly from an instance of the auth provider client, or `null` if the user is not authenticated. -- async `reauthenticate()`: Refetch the authentication data and populate the state. -- async `getToken()`: Returns a JWT. -- `client`: Access the instance of the client which you passed into `AuthProvider`. -- `isAuthenticated`: Determines if the current user has authenticated. -- `hasRole(['admin'])`: Determines if the current user is assigned a role like `"admin"` or assigned to any of the roles in a list such as `['editor', 'author']`. -- `loading`: The auth state is restored asynchronously when the user visits the site for the first time, use this to determine if you have the correct state. - -## Usage in Redwood - -Redwood provides a zeroconf experience when using our Auth package! - -### GraphQL Query and Mutations - -GraphQL requests automatically receive an `Authorization` JWT header when a user is authenticated. - -### Auth Provider API - -If a user is signed in, the `Authorization` token is verified, decoded and available in `context.currentUser` - -```js -import { context } from '@redwoodjs/api' - -console.log(context.currentUser) -// { -// sub: ' -// email: 'user@example.com', -// [...] -// } -``` - -You can map the "raw decoded JWT" into a real user object by passing a `getCurrentUser` function to `createCreateGraphQLHandler` - -Our recommendation is to create a `src/lib/auth.js|ts` file that exports a `getCurrentUser`. (Note: You may already have stub functions.) - -```js -import { getCurrentUser } from 'src/lib/auth' -// Example: -// export const getCurrentUser = async (decoded) => { -// return await db.user.findUnique({ where: { decoded.email } }) -// } -// - -export const handler = createGraphQLHandler({ - schema: makeMergedSchema({ - schemas, - services: makeServices({ services }), - }), - getCurrentUser, -}) -``` - -The value returned by `getCurrentUser` is available in `context.currentUser` - -Use `requireAuth` in your services to check that a user is logged in, -whether or not they are assigned a role, and optionally raise an -error if they're not. - -```js -export const requireAuth = ({ roles }) => { - if (!isAuthenticated()) { - throw new AuthenticationError("You don't have permission to do that.") - } - - if (roles && !hasRole(roles)) { - throw new ForbiddenError("You don't have access to do that.") - } -}} -} -``` - -### Auth Provider Specific Integration - -#### Auth0 - -If you're using Auth0 you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of a JWT token, and Redwood expects to receive a JWT token. - -+++ View Auth0 Options - -#### Role-based access control (RBAC) in Auth0 - -[Role-based access control (RBAC)](https://auth0.com/docs/authorization/concepts/rbac) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. - -Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". - -#### App metadata in Auth0 - -Auth0 stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. - -Create and manage roles for your application in Auth0's "User & Role" management views. You can then assign these roles to users. - -However, that info is not immediately available on the user's `app_metadata` or to RedwoodJS when authenticating. - -If you assign your user the "admin" role in Auth0, you will want your user's `app_metadata` to look like: - -``` -{ - "roles": [ - "admin" - ] -} -``` - -To set this information and make it available to RedwoodJS, you can use [Auth0 Rules](https://auth0.com/docs/rules). - -#### Auth0 Rules for App Metadata - -RedwoodJS needs `app_metadata` to 1) contain the role information and 2) be present in the JWT that is decoded. - -To accomplish these tasks, you can use [Auth0 Rules](https://auth0.com/docs/rules) to add them as custom claims on your JWT. - -#### Add Authorization Roles to App Metadata Rule - -Your first rule will `Add Authorization Roles to App Metadata`. - -```js -/// Add Authorization Roles to App Metadata -function (user, context, callback) { - auth0.users.updateAppMetadata(user.user_id, context.authorization) - .then(function(){ - callback(null, user, context); - }) - .catch(function(err){ - callback(err); - }); - } -``` - -Auth0 exposes the user's roles in `context.authorization`. This rule simply copies that information into the user's `app_metadata`, such as: - -``` -{ - "roles": [ - "admin" - ] -} -``` - -However, now you must include the `app_metadata` on the user's JWT that RedwoodJS will decode. - -#### Add App Metadata to JWT Rule in Auth0 - -Therefore, your second rule will `Add App Metadata to JWT`. - -You can add `app_metadata` to the `idToken` or `accessToken`. - -Adding to `idToken` will make the make app metadata accessible to RedwoodJS `getUserMetadata` which for Auth0 calls the auth client's `getUser`. - -Adding to `accessToken` will make the make app metadata accessible to RedwoodJS when decoding the JWT via `getToken`. - -While adding to `idToken` is optional, you _must_ add to `accessToken`. - -To keep your custom claims from colliding with any reserved claims or claims from other resources, you must give them a [globally unique name using a namespaced format](https://auth0.com/docs/tokens/guides/create-namespaced-custom-claims). Otherwise, Auth0 will _not_ add the information to the token(s). - -Therefore, with a namespace of "https://example.com", the `app_metadata` on your token should look like: - -```js -"https://example.com/app_metadata": { - "authorization": { - "roles": [ - "admin" - ] - } -}, -``` - -To set this namespace information, use the following function in your rule: - -```js -function (user, context, callback) { - var namespace = 'https://example.com/'; - - // adds to idToken, i.e. userMetadata in RedwoodJS - context.idToken[namespace + 'app_metadata'] = {}; - context.idToken[namespace + 'app_metadata'].authorization = { - groups: user.app_metadata.groups, - roles: user.app_metadata.roles, - permissions: user.app_metadata.permissions - }; - - context.idToken[namespace + 'user_metadata'] = {}; - - // accessToken, i.e. the decoded JWT in RedwoodJS - context.accessToken[namespace + 'app_metadata'] = {}; - context.accessToken[namespace + 'app_metadata'].authorization = { - groups: user.app_metadata.groups, - roles: user.app_metadata.roles, - permissions: user.app_metadata.permissions - }; - - context.accessToken[namespace + 'user_metadata'] = {}; - - return callback(null, user, context); -} -``` - -Now, your `app_metadata` with `authorization` and `role` information will be on the user's JWT after logging in. - -#### Add Application hasRole Support in Auth0 - -If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. - -If your roles are on a namespaced `app_metadata` claim, then `parseJWT` provides an option to provide this value. - -```js -// api/src/lib/auth.js` -const NAMESPACE = 'https://example.com' - -const currentUserWithRoles = async (decoded) => { - const currentUser = await userByUserId(decoded.sub) - return { - ...currentUser, - roles: parseJWT({ decoded: decoded, namespace: NAMESPACE }).roles, - } -} - -export const getCurrentUser = async (decoded, { type, token }) => { - try { - requireAccessToken(decoded, { type, token }) - return currentUserWithRoles(decoded) - } catch (error) { - return decoded - } -} -``` - -+++ - -#### Magic.Link - -The Redwood API does not include the functionality to decode Magic.link authentication tokens, so the client is initiated and decodes the tokens inside of `getCurrentUser`. - -+++ View Magic.link Options - -##### Installation - -First, you must manually install the **Magic Admin SDK** in your project's `api/package.json`. - -```terminal -yarn workspace api add @magic-sdk/admin -``` - -##### Setup - -To get your application running _without setting up_ `Prisma`, get your `SECRET KEY` from [dashboard.magic.link](https://dashboard.magic.link/). Then add `MAGICLINK_SECRET` to your `.env`. - -```js -// redwood/api/src/lib/auth.js|ts -import { Magic } from '@magic-sdk/admin' - -export const getCurrentUser = async (_decoded, { token }) => { - const mAdmin = new Magic(process.env.MAGICLINK_SECRET) - - return await mAdmin.users.getMetadataByToken(token) -} -``` - -Magic.link recommends using the issuer as the userID to retrieve user metadata via `Prisma` - -```js -// redwood/api/src/lib/auth.ts -import { Magic } from '@magic-sdk/admin' - -export const getCurrentUser = async (_decoded, { token }) => { - const mAdmin = new Magic(process.env.MAGICLINK_SECRET) - const { email, publicAddress, issuer } = await mAdmin.users.getMetadataByToken(token) - - return await db.user.findUnique({ where: { issuer } }) -} -``` - -+++ - -#### Firebase - -You must follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin) part of the "Authenticate Using Google Sign-In with JavaScript" guide. - -+++ View Firebase Options - -#### Role-based access control (RBAC) in Firebase - -Requires a custom implementation. - -#### App metadata in Firebase - -None. - -#### Add Application hasRole Support in Firebase - -Requires a custom implementation. - -#### Auth Providers - -Providers can be configured by specifying `logIn(provider)` and `signUp(provider)`, where `provider` is a **string** of one of the supported providers. - -Supported providers: - -- google.com (Default) -- facebook.com -- github.com -- twitter.com -- microsoft.com -- apple.com - -#### Email & Password Auth in Firebase - -Email/password authentication is supported by calling `login({ email, password })` and `signUp({ email, password })`. - -#### Email link (passwordless sign-in) in Firebase - -In Firebase Console, you must enable "Email link (passwordless sign-in)" with the configuration toggle for the email provider. The authentication sequence for passwordless email links has two steps: - -1. First, an email with the link must be generated and sent to the user. Either using using firebase client sdk (web side) [sendSignInLinkToEmail()](https://firebase.google.com/docs/reference/js/auth.emailauthprovider#example_2_2), which generates the link and sends the email to the user on behalf of your application or alternatively, generate the link using backend admin sdk (api side), see ["Generate email link for sign-in](https://firebase.google.com/docs/auth/admin/email-action-links#generate_email_link_for_sign-in) but it is then your responsibility to send an email to the user containing the link. -2. Second, authentication is completed when the user is redirected back to the application and the AuthProvider's logIn({emailLink, email, providerId: 'emailLink'}) method is called. - -For example, users could be redirected to a dedicated route/page to complete authentication: - -```js -import { useEffect } from 'react' -import { Redirect, routes } from '@redwoodjs/router' -import { useAuth } from '@redwoodjs/auth' - -const EmailSigninPage = () => { - const { loading, hasError, error, logIn } = useAuth() - - const email = window.localStorage.getItem('emailForSignIn') - // TODO: Prompt the user for email if not found in local storage, for example - // if the user opened the email link on a different device. - - const emailLink = window.location.href - - useEffect(() => { - logIn({ - providerId: 'emailLink', - email, - emailLink, - }) - }, []) - - if (loading) { - return
Auth Loading...
- } - - if (hasError) { - console.error(error) - return
Auth Error... check console
- } - - return -} - -export default EmailSigninPage -``` - -#### Custom Token in Firebase - -If you want to [integrate firebase auth with another authentication system](https://firebase.google.com/docs/auth/web/custom-auth), you can use a custom token provider: - -```js -logIn({ - providerId: 'customToken', - customToken, -}) -``` - -Some caveats about using custom tokens: - -- make sure it's actually what you want to use -- remember that the client's firebase authentication state has an independent lifetime than the custom token - -If you want to read more, check out [Demystifying Firebase Auth Tokens](https://medium.com/@jwngr/demystifying-firebase-auth-tokens-e0c533ed330c). - -#### Custom Parameters & Scopes for Google OAuth Provider - -Both `logIn()` and `signUp()` can accept a single argument of either a **string** or **object**. If a string is provided, it should be any of the supported providers (see above), which will configure the defaults for that provider. - -`logIn()` and `signUp()` also accept a single a configuration object. This object accepts `providerId`, `email`, `password`, and `scope` and `customParameters`. (In fact, passing in any arguments ultimately results in this object). You can use this configuration object to pass in values for the optional Google OAuth Provider methods _setCustomParameters_ and _addScope_. - -Below are the parameters that `logIn()` and `signUp()` accept: - -- `providerId`: Accepts one of the supported auth providers as a **string**. If no arguments are passed to `login() / signUp()` this will default to 'google.com'. Provider strings passed as a single argument to `login() / signUp()` will be cast to this value in the object. -- `email`: Accepts a **string** of a users email address. Used in conjunction with `password` and requires that Firebase has email authentication enabled as an option. -- `password`: Accepts a **string** of a users password. Used in conjunction with `email` and requires that Firebase has email authentication enabled as an option. -- `scope`: Accepts an **array** of strings ([Google OAuth Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)), which can be added to the requested Google OAuth Provider. These will be added using the Google OAuth _addScope_ method. -- `customParameters`: accepts an **object** with the [optional parameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#setcustomparameters) for the Google OAuth Provider _setCustomParameters_ method. [Valid parameters](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) include 'hd', 'include_granted_scopes', 'login_hint' and 'prompt'. - -#### Firebase Auth Examples - -- `logIn()/signUp()`: Defaults to Google provider. -- `logIn({providerId: 'github.com'})`: Log in using GitHub as auth provider. -- `signUp({email: "someone@email.com", password: 'some_good_password'})`: Creates a firebase user with email/password. -- `logIn({email: "someone@email.com", password: 'some_good_password'})`: Logs in existing firebase user with email/password. -- `logIn({scopes: ['https://www.googleapis.com/auth/calendar']})`: Adds a scope using the [addScope](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. -- `logIn({ customParameters: { prompt: "consent" } })`: Sets the OAuth custom parameters using [setCustomParameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. - -+++ - -#### Netlify Identity - -[Netlify Identity](https://docs.netlify.com/visitor-access/identity) offers [Role-based access control (RBAC)](https://docs.netlify.com/visitor-access/identity/manage-existing-users/#user-account-metadata). - -+++ View Netlify Identity Options - -#### Role-based access control (RBAC) in Netlify Identity - -Role-based access control (RBAC) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. - -Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". - -#### App metadata in Netlify Identity - -Netlify Identity stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. - -Create and manage roles for your application in Netlify's "Identity" management views. You can then assign these roles to users. - -#### Add Application hasRole Support in Netlify Identity - -If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. - -Netlify will store the user's roles on the `app_metadata` claim and the `parseJWT` function provides an option to extract the roles so they can be assigned to the `currentUser`. - -For example: - -```js -// api/src/lib/auth.js` -export const getCurrentUser = async (decoded) => { - return context.currentUser || { ...decoded, roles: parseJWT({ decoded }).roles } -} -``` - -Now your `currentUser.roles` info will be available to both `requireAuth()` on the api side and `hasRole()` on the web side. - -+++ - -### Role Protection on Web - -You can protect content by role in pages or components via the `useAuth()` hook: - -```js -const { isAuthenticated, hasRole } = useAuth() - -... - -{hasRole('admin') && ( - Admin -)} - -{hasRole(['author', 'editor']) && ( - Admin -)} -``` - -### Routes - -Routes can require authentication by wrapping them in a `` component. An unauthenticated user will be redirected to the page specified in `unauthenticated`. - -```js -import { Router, Route, Private } from '@redwoodjs/router' - -const Routes = () => { - return ( - - - - - - - - - - ) -} -``` - -Routes and Sets can also be restricted by role by specifying `hasRole="role"` or `hasRole={['role', 'another_role']})` in the `` component. A user not assigned the role will be redirected to the page specified in `unauthenticated`. - -```js -import { Router, Route, Private } from '@redwoodjs/router' - -const Routes = () => { - return ( - - - - - - - - - - - - - - - - - - ) -} -``` - -## Contributing - -If you are interested in contributing to the Redwood Auth Package, please [start here](https://github.com/redwoodjs/redwood/blob/main/packages/auth/README.md). diff --git a/docs/versioned_docs/version-1.0/builds.md b/docs/versioned_docs/version-1.0/builds.md deleted file mode 100644 index de342ab643e6..000000000000 --- a/docs/versioned_docs/version-1.0/builds.md +++ /dev/null @@ -1,34 +0,0 @@ -# Builds - -> ⚠ **Work in Progress** ⚠️ -> -> There's more to document here. In the meantime, you can check our [community forum](https://community.redwoodjs.com/search?q=yarn%20rw%20build) for answers. -> -> Want to contribute? Redwood welcomes contributions and loves helping people become contributors. -> You can edit this doc [here](https://github.com/redwoodjs/redwoodjs.com/blob/main/docs/builds.md). -> If you have any questions, just ask for help! We're active on the [forums](https://community.redwoodjs.com/c/contributing/9) and on [discord](https://discord.com/channels/679514959968993311/747258086569541703). - - -## API - -The api side of Redwood is transpiled by Babel into the `./api/dist` folder. - -### steps on Netlify - -To emulate Netlify's build steps locally: - -```bash -yarn rw build api -cd api -yarn zip-it-and-ship-it dist/functions/ zipballs/ -``` - -Each lambda function in `./api/dist/functions` is parsed by zip-it-and-ship-it resulting in a zip file per lambda function that contains all the dependencies required for that lambda function. - ->Note: The `@netlify/zip-it-and-ship-it` package needs to be installed as a dev dependency in `api/`. Use the command `yarn workspace api add -D @netlify/zip-it-and-ship-it`. ->- You can learn more about the package [here](https://www.npmjs.com/package/@netlify/zip-it-and-ship-it). ->- For more information on AWS Serverless Deploy see these [docs](deploy.md#serverless-deploy). - -## Web - -The web side of Redwood is packaged by Webpack into the `./web/dist` folder. diff --git a/docs/versioned_docs/version-1.0/cells.md b/docs/versioned_docs/version-1.0/cells.md deleted file mode 100644 index 0f745b226413..000000000000 --- a/docs/versioned_docs/version-1.0/cells.md +++ /dev/null @@ -1,428 +0,0 @@ -# Cells - -Cells are a declarative approach to data fetching and one of Redwood's signature modes of abstraction. -By providing conventions around data fetching, Redwood can get in between the request and the response to do things like query optimization and more, all without you ever having to change your code. - -While it might seem like there's a lot of magic involved, all a Cell really does is execute a GraphQL query and manage its lifecycle. -The idea is that, by exporting named constants that declare what you want your UI to look like throughout a query's lifecycle, -Redwood can assemble these into a component template at build-time using a Babel plugin. -All without you having to write a single line of imperative code! - -## Generating a Cell - -You can generate a Cell with Redwood's Cell generator: - -```terminal -yarn rw generate cell -``` - -This creates a directory named `Cell` in `web/src/components` with four files: - -```terminal -~/redwood-app$ yarn rw generate cell user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/rw g cell user - ✔ Generating cell files... - ✔ Writing `./web/src/components/UserCell/UserCell.mock.js`... - ✔ Writing `./web/src/components/UserCell/UserCell.stories.js`... - ✔ Writing `./web/src/components/UserCell/UserCell.test.js`... - ✔ Writing `./web/src/components/UserCell/UserCell.js`... -Done in 1.07s. -``` - -### Single Item Cell vs List Cell - -Sometimes you want a Cell that renders a single item, like the example above, and other times you want a Cell that renders a list. -Redwood's Cell generator can do both. - -First, it detects if `` is singular or plural. -For example, to generate a Cell that renders a list of users, run `yarn rw generate cell users`. -Second, for **irregular words** whose singular and plural are identical, such as *equipment* or *pokemon*, you can specify the `--list` flag to tell Redwood to generate a list Cell explicitly: - -``` -yarn rw generate cell equipment --list -``` - -## Cells in-depth - -We'll go over each of these files in detail. But know that the file appended with just `.js` (in the example above, `UserCell.js`) contains all your Cell's logic. - -Off the bat, this file exports five constants: `QUERY`, `Loading` , `Empty` , `Failure` and `Success`. The root query in `QUERY` is the same as `` so that, if you're generating a cell based on a model in your `schema.prisma`, you can get something out of the database right away. But there's a good chance you won't generate your Cell this way, so if you need to, make sure to change the root query. See the [Cells](tutorial/chapter2/cells.md#our-first-cell) section of the Tutorial for a great example of this. - -## Usage - -With Cells, you have a total of seven exports to work with: - -| Name | Type | Description | -| :------------ | :----------------- | :----------------------------------------------------------- | -| `QUERY` | `string\|function` | The query to execute | -| `beforeQuery` | `function` | Lifecycle hook; prepares variables and options for the query | -| `isEmpty` | `function` | Lifecycle hook; decides if Cell should render Empty | -| `afterQuery` | `function` | Lifecycle hook; sanitizes data returned from the query | -| `Loading` | `component` | If the request is in flight, render this component | -| `Empty` | `component` | If there's no data (`null` or `[]`), render this component | -| `Failure` | `component` | If something went wrong, render this component | -| `Success` | `component` | If the data has loaded, render this component | - -Only `QUERY` and `Success` are required. If you don't export `Empty`, empty results are sent to `Success`, and if you don't export `Failure`, error is output to the console. - -In addition to displaying the right component, Cells also make sure to funnel the right props to the right component. `Loading`, `Empty`, `Failure`, and `Success` all have access to the props passed down from the Cell in good ol' React fashion, and most of `useQuery`'s return (more on that below). In addition to all those props, `Empty` and `Success` also get the `data` returned from the query and an `updating` boolean prop saying whether the Cell is currently fetching new data or not. `Failure` also gets `updating` and exclusive access to `error` and `errorCode`. - -With this many props coming in, there's a risk of name clashing. A couple things to look out for are: - -- Your Cell has a prop with the same name as root-level data returned by your query. - - In this case, the root-level data overrides your prop. But since props double as query variables, you can destructure the `variables` prop that `useQuery` returns to retrieve it. Or you can just rename the prop on the Cell! - -- Your Cell has props or query results with the same name as any of `useQuery`'s returns. - - In this case, `useQuery`'s returns overwrite the props and results. - -We mentioned above that Cells receive "most" of what's returned from `useQuery`. You can see exactly what `useQuery` returns in Apollo Client's [API reference](https://www.apollographql.com/docs/react/api/react/hooks/#result). Note that, as we just mentioned, `error` and `data` get some special treatment. - -### QUERY - -`QUERY` can be a string or a function. Note that it's totally more than ok to have more than one root query. Here's an example of that: - -```javascript{7-10} -export const QUERY = gql`{ - query { - posts { - id - title - } - authors { - id - name - } - } -} -``` - -So in this case, both `posts` and `authors` would be available to `Success`: - -```js -export const Success = ({ posts, authors }) => { - // render logic with posts and authors -} -``` - -If `QUERY` is a function, it has to return a valid GraphQL document. -Use a function if your queries need to be more dynamic: - - -But what about variables? Well, Cells are setup to use any props they receive from their parent as variables (things are setup this way in `beforeQuery`). For example, here `BlogPostCell` takes a prop, `numberToShow`, so `numberToShow` is just available to your `QUERY`: - -```javascript{7} -import BlogPostsCell from 'src/components/BlogPostsCell' - -const HomePage = () => { - return ( -
-

Home

- -
- ) -} - -export default HomePage -``` - -```javascript{2-3} -export const QUERY = gql` - query($numberToShow: Int!) { - posts(numberToShow: $numberToShow) { - id - title - } - } -` -``` - -This means you can think backwards about your Cell's props from your SDL: whatever the variables in your SDL are, that's what your Cell's props should be. - -### beforeQuery - -`beforeQuery` is a lifecycle hook. The best way to think about it is as an API for configuring Apollo Client's `Query` component (so you might want to check out Apollo's [docs](https://www.apollographql.com/docs/react/api/react-components/#query) for it). - -By default, `beforeQuery` gives any props passed from the parent component to `Query` so that they're available as variables for `QUERY`. It'll also set the fetch policy to `'cache-and-network'` since we felt it matched the behavior users want most of the time. - -```javascript -export const beforeQuery = (props) => { - return { - variables: props, - fetchPolicy: 'cache-and-network' - } -} -``` - -For example, if you wanted to turn on Apollo's polling option, and prevent caching, you could export something like this (see Apollo's docs on [polling](https://www.apollographql.com/docs/react/data/queries/#polling) and [caching](https://www.apollographql.com/docs/react/data/queries/#setting-a-fetch-policy)) - - -```javascript -export const beforeQuery = (props) => { - return { variables: props, fetchPolicy: 'no-cache', pollInterval: 2500 } -} -``` - -### isEmpty - -`isEmpty` is an optional lifecycle hook. It returns a boolean to indicate if Cell is empty. Use it to override the [default check](#empty). - -It receives the `data`, and the default check reference `isDataEmpty`, so it's possible to extend the default check with custom logic. - -```javascript -export const isEmpty = (data, { isDataEmpty }) => - isDataEmpty(data) || data?.blog?.status === 'hidden' -``` - -### afterQuery - -`afterQuery` is a lifecycle hook. It runs just before data gets to `Success`. -Use it to sanitize data returned from `QUERY` before it gets there. - -By default, `afterQuery` just returns the data as it is: - -```javascript -export const afterQuery = (data) => ({...data}) -``` - -### Loading - -If there's no cached data and the request is in flight, a Cell renders `Loading`. - -For a production example, navigate to [predictcovid.com](https://predictcovid.com), the first site made with Redwood. Usually, when you first navigate there, you'll see most of the dashboard spinning. Those are `Loading` components in action! - -When you're developing locally, you can catch your Cell waiting to hear back for a moment if set your speed in the Inspector's **Network** tab to something like "Slow 3G". - -But why bother with Slow 3G when Redwood comes with Storybook? Storybook makes developing components like `Loading` (and `Failure`) a breeze. We don't have to put up with hacky workarounds like Slow 3G or intentionally breaking our app just to develop our components. - -### Empty - -A Cell renders this component if there's no data. - -What do we mean by no data? We mean if the response is 1) `null` or 2) an empty array (`[]`). There's actually four functions in [createCell.tsx](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx) dedicated just to figuring this out: - -```javascript -// createCell.tsx - -const isDataNull = (data: DataObject) => { - return dataField(data) === null -} - -const isDataEmptyArray = (data: DataObject) => { - const field = dataField(data) - return Array.isArray(field) && field.length === 0 -} - -const dataField = (data: DataObject) => { - return data[Object.keys(data)[0]] -} - -const isEmpty = (data: DataObject) => { - return isDataNull(data) || isDataEmptyArray(data) -} -``` - -### Failure - -A Cell renders this component if something went wrong. You can quickly see this in action (it's easy to break things) if you add a nonsense field to your `QUERY`: - -```javascript{6} -const QUERY = gql` - query { - posts { - id - title - nonsense - } - } -` -``` - -But, like `Loading`, Storybook is probably a better place to develop this. - - - -In this example, we use the `errorCode` to conditionally render the error heading title, and we also use it for our translation string. -```jsx -export const Failure = ({ error, errorCode }: CellFailureProps) => { - const { t } = useTranslation() - return ( -
- {errorCode === 'NO_CONFIG' ?

NO_CONFIG

:

ERROR

} - Error: {error.message} - Code: {errorCode} - {t(`error.${errorCode}`)} -
- ) -} -``` - -### Success - -If everything went well, a Cell renders `Success`. - -As mentioned, Success gets exclusive access to the `data` prop. But if you try to destructure it from props, you'll notice that it doesn't exist. This is because Redwood adds another layer of convenience: in [createCell.tsx](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx#L149), Redwood spreads `data` (using the spread operator, `...`) into `Success` so that you can just destructure whatever data you were expecting from your `QUERY` directly. - -So, if you're querying for `posts` and `authors`, instead of doing: - -```js -export const Success = ({ data }) => { - const { posts, authors } = data - - // render logic with posts and authors - ... -} -``` - -Redwood does: - -```js -export const Success = ({ posts, authors }) => { - // render logic with posts and authors - ... -} -``` - -Note that you can still pass any other props to `Success`. After all, it's still just a React component. - -### When should I use a Cell? - -Whenever you want to fetch data. Let Redwood juggle what's displayed when. You just focus on what those things should look like. - -While you can use a Cell whenever you want to fetch data, it's important to note that you don't have to. You can do anything you want! For example, for one-off queries, there's always `useApolloClient`. This hook returns the client, which you can use to execute queries, among other things: - -```javascript -// In a react component... - -client = useApolloClient() - -client.query({ - query: gql` - ... - ` -}) -``` - -### Can I Perform a Mutation in a Cell? - -Absolutely. We do so in our [example todo app](https://github.com/redwoodjs/example-todo/blob/f29069c9dc89fa3734c6f99816442e14dc73dbf7/web/src/components/TodoListCell/TodoListCell.js#L26-L44). -We also don't think it's an anti-pattern to do so. Far from it—your cells might end up containing a lot of logic and really serve as the hub of your app in many ways. - -It's also important to remember that, besides exporting certain things with certain names, there aren't many rules around Cells—everything you can do in a regular component still goes. - -## How Does Redwood Know a Cell is a Cell? - -You just have to end a filename in "Cell" right? Well, while that's basically correct, there is one other thing you should know. - -Redwood looks for all files ending in "Cell" (so if you want your component to be a Cell, its filename does have to end in "Cell"), but if the file 1) doesn't export a const named `QUERY` and 2) has a default export, then it'll be skipped. - -When would you want to do this? If you just want a file to end in "Cell" for some reason. Otherwise, don't worry about it! - - - - - - -## Advanced Example: Implementing a Cell Yourself - -If we didn't do all that built-time stuff for you, how might you go about implementing a Cell yourself? - -Consider the [example from the Tutorial](tutorial/chapter2/cells.md#our-first-cell) where we're fetching posts: - -```javascript -export const QUERY = gql` - query { - posts { - id - title - body - createdAt - } - } -` - -export const Loading = () =>
Loading...
- -export const Empty = () =>
No posts yet!
- -export const Failure = ({ error }) => ( -
Error loading posts: {error.message}
-) - -export const Success = ({ posts }) => { - return posts.map((post) => ( -
-

{post.title}

-
{post.body}
-
- )) -} -``` - -And now let's say that Babel isn't going to come along and assemble our exports. What might we do? - -We'd probably do something like this: - - -```javascript -const QUERY = gql` - query { - posts { - id - title - body - createdAt - } - } -` - -const Loading = () =>
Loading...
- -const Empty = () =>
No posts yet!
- -const Failure = ({ error }) => ( -
Error loading posts: {error.message}
-) - -const Success = ({ posts }) => { - return posts.map((post) => ( -
-

{post.title}

-
{post.body}
-
- )) -} - -const isEmpty = (data) => { - return isDataNull(data) || isDataEmptyArray(data) -} - -export const Cell = () => { - return ( - - {({ error, loading, data }) => { - if (error) { - if (Failure) { - return - } else { - console.error(error) - } - } else if (loading) { - return - } else if (data) { - if (typeof Empty !== 'undefined' && isEmpty(data)) { - return - } else { - return - } - } else { - throw 'Cannot render Cell: graphQL success but `data` is null' - } - }} - - ) -} -``` - -That's a lot of code. A lot of imperative code too. - -We're basically just dumping the contents of [createCell.tsx](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx) into this file. Can you imagine having to do this every time you wanted to fetch data that might be delayed in responding? Yikes. diff --git a/docs/versioned_docs/version-1.0/cli-commands.md b/docs/versioned_docs/version-1.0/cli-commands.md deleted file mode 100644 index df9ab57690d4..000000000000 --- a/docs/versioned_docs/version-1.0/cli-commands.md +++ /dev/null @@ -1,1950 +0,0 @@ -# Command Line Interface - -The following is a comprehensive reference of the Redwood CLI. You can get a glimpse of all the commands by scrolling the aside to the right. - -The Redwood CLI has two entry-point commands: - -1. **redwood** (alias `rw`), which is for developing an application, and -2. **redwood-tools** (alias `rwt`), which is for contributing to the framework. - -This document covers the `redwood` command . For `redwood-tools`, see [Contributing](https://github.com/redwoodjs/redwood/blob/main/CONTRIBUTING.md#cli-reference-redwood-tools) in the Redwood repo. - -**A Quick Note on Syntax** - -We use [yargs](http://yargs.js.org/) and borrow its syntax here: - -``` -yarn redwood generate page [path] --option -``` - -- `redwood g page` is the command. -- `` and `[path]` are positional arguments. - - `<>` denotes a required argument. - - `[]` denotes an optional argument. -- `--option` is an option. - -Every argument and option has a type. Here `` and `[path]` are strings and `--option` is a boolean. - -You'll also sometimes see arguments with trailing `..` like: - -``` -yarn redwood build [side..] -``` - -The `..` operator indicates that the argument accepts an array of values. See [Variadic Positional Arguments](https://github.com/yargs/yargs/blob/master/docs/advanced.md#variadic-positional-arguments). - -## build - -Build for production. - -```terminal -yarn redwood build [side..] -``` - -We use Babel to transpile the api side into `./api/dist` and Webpack to package the web side into `./web/dist`. - -| Arguments & Options | Description | -| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `side` | Which side(s) to build. Choices are `api` and `web`. Defaults to `api` and `web` | -| `--stats` | Use [Webpack Bundle Analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) to visualize the size of Webpack output files via an interactive zoomable treemap | -| `--verbose, -v` | Print more information while building | - -**Usage** - -See [Builds](builds.md). - -**Example** - -Running `yarn redwood build` without any arguments generates the Prisma client and builds both sides of your project: - -```terminal -~/redwood-app$ yarn redwood build -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood build - ✔ Generating the Prisma client... - ✔ Building "api"... - ✔ Building "web"... -Done in 17.37s. -``` - -Files are output to each side's `dist` directory: - -```plaintext{2,6} -├── api -│ ├── dist -│ ├── prisma -│ └── src -└── web - ├── dist - ├── public - └── src -``` - -## check (alias diagnostics) - -Get structural diagnostics for a Redwood project (experimental). - -``` -yarn redwood check -``` - -**Example** - -```terminal -~/redwood-app$ yarn redwood check -yarn run v1.22.4 -web/src/Routes.js:14:5: error: You must specify a 'notfound' page -web/src/Routes.js:14:19: error: Duplicate Path -web/src/Routes.js:15:19: error: Duplicate Path -web/src/Routes.js:17:40: error: Page component not found -web/src/Routes.js:17:19: error (INVALID_ROUTE_PATH_SYNTAX): Error: Route path contains duplicate parameter: "/{id}/{id}" -``` - -## console (alias c) - -Launch an interactive Redwood shell (experimental): - -- This has not yet been tested on Windows. -- The Prisma Client must be generated _prior_ to running this command, e.g. `yarn redwood prisma generate`. This is a known issue. - -``` -yarn redwood console -``` - -Right now, you can only use the Redwood console to interact with your database: - -**Example** - -```terminal -~/redwood-app$ yarn redwood console -yarn run v1.22.4 -> await db.user.findMany() -> [ { id: 1, email: 'tom@redwoodjs.com', name: 'Tom' } ] -``` - -## dataMigrate - -Data migration tools. - -``` -yarn redwood dataMigrate -``` - -| Command | Description | -| :-------- | :------------------------------------------------------------------------------------------ | -| `install` | Appends `DataMigration` model to `schema.prisma`, creates `api/db/dataMigrations` directory | -| `up` | Executes outstanding data migrations | - -### dataMigrate install - -- Appends a `DataMigration` model to `schema.prisma` for tracking which data migrations have already run. -- Creates a DB migration using `yarn redwood prisma migrate dev --create-only create_data_migrations`. -- Creates `api/db/dataMigrations` directory to contain data migration scripts - -```terminal -yarn redwood dataMigrate install -``` - -### dataMigrate up - -Executes outstanding data migrations against the database. Compares the list of files in `api/db/dataMigrations` to the records in the `DataMigration` table in the database and executes any files not present. - -If an error occurs during script execution, any remaining scripts are skipped and console output will let you know the error and how many subsequent scripts were skipped. - -```terminal -yarn redwood dataMigrate up -``` - -## dev - -Start development servers for api and web. - -```terminal -yarn redwood dev [side..] -``` - -`yarn redwood dev api` starts the Redwood dev server and `yarn redwood dev web` starts the Webpack dev server with Redwood's config. - -| Argument | Description | -| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `side` | Which dev server(s) to start. Choices are `api` and `web`. Defaults to `api` and `web` | -| `--forward, --fwd` | String of one or more Webpack Dev Server config options. See example usage below. See the [Redwood Webpack Doc](webpack-configuration.md#webpack-dev-server) for more details and examples. | - -**Usage** - -If you're only working on your sdl and services, you can run just the api server to get GraphQL Playground on port 8911: - -```bash -~/redwood-app$ yarn redwood dev api -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood dev api -$ /redwood-app/node_modules/.bin/dev-server -15:04:51 api | Listening on http://localhost:8911 -15:04:51 api | Watching /home/dominic/projects/redwood/redwood-app/api -15:04:51 api | -15:04:51 api | Now serving -15:04:51 api | -15:04:51 api | ► http://localhost:8911/graphql/ -``` - -Using `--forward` (alias `--fwd`), you can pass one or more Webpack Dev Server [config options](https://webpack.js.org/configuration/dev-server/). The following will run the dev server, set the port to `1234`, and disable automatic browser opening. - -```bash -~/redwood-app$ yarn redwood dev --fwd="--port=1234 --open=false" -``` - -You may need to access your dev application from a different host, like your mobile device or an SSH tunnel. To resolve the “Invalid Host Header” message, run the following: - -```bash -~/redwood-app$ yarn redwood dev --fwd="--allowed-hosts all" -``` - -For the full list of Webpack Dev Server settings, see [this documentation](https://webpack.js.org/configuration/dev-server/). - -For the full list of Server Configuration settings, see [this documentation](app-configuration-redwood-toml.md#api). - -## deploy - -Deploy your redwood project to a hosting provider target. - -**Netlify, Vercel, and Render** - -For hosting providers that auto deploy from Git, the deploy command runs the set of steps to build, apply production DB changes, and apply data migrations. In this context, it is often referred to as a Build Command. _Note: for Render, which uses traditional infrastructure, the command also starts Redwood's api server._ - -**AWS** - -This command runs the steps to both build your project _and_ deploy it to AWS. - -``` -yarn redwood deploy -``` - -| Commands | Description | -|:------------------------------|:-----------------------------------------| -| `serverless ` | Deploy to AWS using Serverless framework | -| `netlify [...commands]` | Build command for Netlify deploy | -| `render [...commands]` | Build command for Render deploy | -| `vercel [...commands]` | Build command for Vercel deploy | - -### deploy serverless - -Deploy to AWS CloudFront and Lambda using [Serverless](https://www.serverless.com/) framework - -``` -yarn redwood deploy serverless -``` - -| Options & Arguments | Description | -|:--------------------|:--------------------------------------------------------------------------------------------------------------------------------------------| -| `--side` | which Side(s)to deploy [choices: "api", "web"] [default: "web","api"] | -| `--stage` | serverless stage, see [serverless stage docs](https://www.serverless.com/blog/stages-and-environments) [default: "production"] | -| `--pack-only` | Only package the build for deployment | -| `--first-run` | Use this flag the first time you deploy. The first deploy wizard will walk you through configuring your web side to connect to the api side | - - -### deploy netlify - -Build command for Netlify deploy - -``` -yarn redwood deploy netlify -``` - -| Options | Description | -| :--------------------- | :-------------------------------------------------- | -| `--build` | Build for production [default: "true"] | -| `--prisma` | Apply database migrations [default: "true"] | -| `--data-migrate, --dm` | Migrate the data in your database [default: "true"] | - -**Example** -The following command will build, apply Prisma DB migrations, and skip data migrations. - -``` -yarn redwood deploy netlify --no-data-migrate -``` - -### deploy render - -Build (web) and Start (api) command for Render deploy. (For usage instructions, see the Render [Deploy Redwood](https://render.com/docs/deploy-redwood) doc.) - -``` -yarn redwood deploy render -``` - -| Options & Arguments | Description | -| :--------------------- | :-------------------------------------------------- | -| `side` | select side to build [choices: "api", "web"] | -| `--prisma` | Apply database migrations [default: "true"] | -| `--data-migrate, --dm` | Migrate the data in your database [default: "true"] | -| `--serve` | Run server for api in production [default: "true"] | - -**Example** -The following command will build the Web side for static-site CDN deployment. - -``` -yarn redwood deploy render web -``` - -The following command will apply Prisma DB migrations, run data migrations, and start the api server. - -``` -yarn redwood deploy render api -``` - -### deploy vercel - -Build command for Vercel deploy - -``` -yarn redwood deploy vercel -``` - -| Options | Description | -| :--------------------- | :-------------------------------------------------- | -| `--build` | Build for production [default: "true"] | -| `--prisma` | Apply database migrations [default: "true"] | -| `--data-migrate, --dm` | Migrate the data in your database [default: "true"] | - -**Example** -The following command will build, apply Prisma DB migrations, and skip data migrations. - -``` -yarn redwood deploy vercel --no-data-migrate -``` - -## destroy (alias d) - -Rollback changes made by the generate command. - -``` -yarn redwood d -``` - -| Command | Description | -| :------------------- | :------------------------------------------------------------------------------ | -| `cell ` | Destroy a cell component | -| `component ` | Destroy a component | -| `function ` | Destroy a Function | -| `layout ` | Destroy a layout component | -| `page [path]` | Destroy a page and route component | -| `scaffold ` | Destroy pages, SDL, and Services files based on a given DB schema Model | -| `sdl ` | Destroy a GraphQL schema and service component based on a given DB schema Model | -| `service ` | Destroy a service component | - -## exec - -Execute scripts generated by [`yarn redwood generate script `](#generate-script) to run one-off operations, long-running jobs, or utility scripts. - -**Usage** - -You can pass any flags to the command and use them within your script: - -``` -❯ yarn redwood exec syncStripeProducts foo --firstParam 'hello' --two 'world' - -[18:13:56] Generating Prisma client [started] -[18:13:57] Generating Prisma client [completed] -[18:13:57] Running script [started] -:: Executing script with args :: -{ _: [ 'exec', 'foo' ], firstParam: 'hello', two: 'world', '$0': 'rw' } -[18:13:58] Running script [completed] -✨ Done in 4.37s. -``` - -**Examples of CLI scripts:** - -- One-off scripts—such as syncing your Stripe products to your database -- A background worker you can off-load long running tasks -- Custom seed scripts for your application during development - -See [this how to](how-to/background-worker.md) for an example of using exec to run a background worker. - -## generate (alias g) - -Save time by generating boilerplate code. - -``` -yarn redwood generate -``` - -Some generators require that their argument be a model in your `schema.prisma`. When they do, their argument is named ``. - -| Command | Description | -| ---------------------- | ----------------------------------------------------------------------------------------------------- | -| `cell ` | Generate a cell component | -| `component ` | Generate a component component | -| `dataMigration ` | Generate a data migration component | -| `deploy ` | Generate a deployment configuration | -| `function ` | Generate a Function | -| `layout ` | Generate a layout component | -| `page [path]` | Generate a page component | -| `scaffold ` | Generate Pages, SDL, and Services files based on a given DB schema Model. Also accepts `` | -| `sdl ` | Generate a GraphQL schema and service object | -| `secret` | Generate a secret key using a cryptographically-secure source of entropy | -| `service ` | Generate a service component | -| `types` | Generate types and supplementary code | -| `script ` | Generate a script that can use your services/libs to execute with `redwood exec script ` | - -### TypeScript generators - -If your project is configured for TypeScript (see [TypeScript docs](typescript.md)), the generators will automatically detect and generate `.ts`/`.tsx` files for you - -**Undoing a Generator with a Destroyer** - -Most generate commands (i.e., everything but `yarn redwood generate dataMigration`) can be undone by their corresponding destroy command. For example, `yarn redwood generate cell` can be undone with `yarn redwood d cell`. - -### generate cell - -Generate a cell component. - -```terminal -yarn redwood generate cell -``` - -Cells are signature to Redwood. We think they provide a simpler and more declarative approach to data fetching. - -| Arguments & Options | Description | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | Name of the cell | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | -| `--list` | Use this flag to generate a list cell. This flag is needed when dealing with irregular words whose plural and singular is identical such as equipment or pokemon | -| `--tests` | Generate test files [default: true] | -| `--stories` | Generate Storybook files [default: true] | - -**Usage** - -The cell generator supports both single items and lists. See the [Single Item Cell vs List Cell](cells.md#single-item-cell-vs-list-cell) section of the Cell documentation. - -See the [Cells](tutorial/chapter2/cells.md) section of the Tutorial for usage examples. - -**Destroying** - -``` -yarn redwood d cell -``` - -**Example** - -Generating a user cell: - -```terminal -~/redwood-app$ yarn redwood generate cell user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g cell user - ✔ Generating cell files... - ✔ Writing `./web/src/components/UserCell/UserCell.test.js`... - ✔ Writing `./web/src/components/UserCell/UserCell.js`... -Done in 1.00s. -``` - -A cell defines and exports four constants: `QUERY`, `Loading`, `Empty`, `Failure`, and `Success`: - -```javascript -// ./web/src/components/UserCell/UserCell.js - -export const QUERY = gql` - query { - user { - id - } - } -` - -export const Loading = () =>
Loading...
- -export const Empty = () =>
Empty
- -export const Failure = ({ error }) =>
Error: {error.message}
- -export const Success = ({ user }) => { - return JSON.stringify(user) -} -``` - -### generate component - -Generate a component. - -```terminal -yarn redwood generate component -``` - -Redwood loves function components and makes extensive use of React Hooks, which are only enabled in function components. - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------ | -| `name` | Name of the component | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | -| `--tests` | Generate test files [default: true] | -| `--stories` | Generate Storybook files [default: true] | - -**Destroying** - -``` -yarn redwood d component -``` - -**Example** - -Generating a user component: - -```terminal -~/redwood-app$ yarn redwood generate component user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g component user - ✔ Generating component files... - ✔ Writing `./web/src/components/User/User.test.js`... - ✔ Writing `./web/src/components/User/User.js`... -Done in 1.02s. -``` - -The component will export some jsx telling you where to find it. - -```javascript -// ./web/src/components/User/User.js - -const User = () => { - return ( -
-

{'User'}

-

{'Find me in ./web/src/components/User/User.js'}

-
- ) -} - -export default User -``` - -### generate dataMigration - -Generate a data migration script. - -``` -yarn redwood generate dataMigration -``` - -Creates a data migration script in `api/db/dataMigrations`. - -| Arguments & Options | Description | -| :------------------ | :----------------------------------------------------------------------- | -| `name` | Name of the data migration, prefixed with a timestamp at generation time | - -**Usage** - -See the [Data Migration](data-migrations.md) docs. - -**Usage** - -See the [Deploy](deploy.md) docs. - -### generate directive - -Generate a directive. - -```terminal -yarn redwood generate directive -``` - -| Arguments & Options | Description | -| -------------------- | --------------------------------------------------------------------- | -| `name` | Name of the directive | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files (defaults to your projects language target) | -| `--type` | Directive type [Choices: "validator", "transformer"] | - -**Usage** - -See [Redwood Directives](directives.md). - -**Example** - -Generating a `myDirective` directive using the interactive command: - -```terminal -yarn rw g directive myDirective - -? What type of directive would you like to generate? › - Use arrow-keys. Return to submit. -❯ Validator - Implement a validation: throw an error if criteria not met to stop execution - Transformer - Modify values of fields or query responses -``` - -### generate function - -Generate a Function. - -``` -yarn redwood generate function -``` - -Not to be confused with Javascript functions, Capital-F Functions are meant to be deployed to serverless endpoints like AWS Lambda. - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------ | -| `name` | Name of the function | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | - -**Usage** - -See the [Custom Function](how-to/custom-function.md) how to. - -**Destroying** - -``` -yarn redwood d function -``` - -**Example** - -Generating a user function: - -```terminal -~/redwood-app$ yarn redwood generate function user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g function user - ✔ Generating function files... - ✔ Writing `./api/src/functions/user.js`... -Done in 16.04s. -``` - -Functions get passed `context` which provides access to things like the current user: - -```javascript -// ./api/src/functions/user.js - -export const handler = async (event, context) => { - return { - statusCode: 200, - body: `user function`, - } -} -``` - -Now if we run `yarn redwood dev api`: - -```plaintext{11} -~/redwood-app$ yarn redwood dev api -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood dev api -$ /redwood-app/node_modules/.bin/dev-server -17:21:49 api | Listening on http://localhost:8911 -17:21:49 api | Watching /home/dominic/projects/redwood/redwood-app/api -17:21:49 api | -17:21:49 api | Now serving -17:21:49 api | -17:21:49 api | ► http://localhost:8911/graphql/ -17:21:49 api | ► http://localhost:8911/user/ -``` - -### generate layout - -Generate a layout component. - -```terminal -yarn redwood generate layout -``` - -Layouts wrap pages and help you stay DRY. - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------ | -| `name` | Name of the layout | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | -| `--tests` | Generate test files [default: true] | -| `--stories` | Generate Storybook files [default: true] | -| `--skipLink` | Generate a layout with a skip link [default: false] | - -**Usage** - -See the [Layouts](tutorial/chapter1/layouts.md) section of the tutorial. - -**Destroying** - -``` -yarn redwood d layout -``` - -**Example** - -Generating a user layout: - -```terminal -~/redwood-app$ yarn redwood generate layout user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g layout user - ✔ Generating layout files... - ✔ Writing `./web/src/layouts/UserLayout/UserLayout.test.js`... - ✔ Writing `./web/src/layouts/UserLayout/UserLayout.js`... -Done in 1.00s. -``` - -A layout will just export it's children: - -```javascript -// ./web/src/layouts/UserLayout/UserLayout.test.js - -const UserLayout = ({ children }) => { - return <>{children} -} - -export default UserLayout -``` - -### generate model - -Generate a RedwoodRecord model. - -```terminal -yarn redwood generate model -``` - -| Arguments & Options | Description | -| ------------------- | ------------------------------------ | -| `name` | Name of the model (in schema.prisma) | -| `--force, -f` | Overwrite existing files | - -**Usage** - -See the [RedwoodRecord docs](redwoodrecord.md). - -**Example** - -```terminal -~/redwood-app$ yarn redwood generate model User -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g model User - ✔ Generating model file... - ✔ Successfully wrote file `./api/src/models/User.js` - ✔ Parsing datamodel, generating api/src/models/index.js... - - Wrote /Users/rob/Sites/redwoodjs/redwood_record/.redwood/datamodel.json - Wrote /Users/rob/Sites/redwoodjs/redwood_record/api/src/models/index.js - -✨ Done in 3.74s. -``` - -Generating a model automatically runs `yarn rw record init` as well. - -### generate page - -Generates a page component and updates the routes. - -```terminal -yarn redwood generate page [path] -``` - -`path` can include a route parameter which will be passed to the generated -page. The syntax for that is `/path/to/page/{routeParam}/more/path`. You can -also specify the type of the route parameter if needed: `{routeParam:Int}`. If -`path` isn't specified, or if it's just a route parameter, it will be derived -from `name` and the route parameter, if specified, will be added to the end. - -This also updates `Routes.js` in `./web/src`. - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------ | -| `name` | Name of the page | -| `path` | URL path to the page. Defaults to `name` | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | -| `--tests` | Generate test files [default: true] | -| `--stories` | Generate Storybook files [default: true] | - -**Destroying** - -``` -yarn redwood d page [path] -``` - -**Examples** - -Generating a home page: - -```plaintext -~/redwood-app$ yarn redwood generate page home / -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g page home / - ✔ Generating page files... - ✔ Writing `./web/src/pages/HomePage/HomePage.test.js`... - ✔ Writing `./web/src/pages/HomePage/HomePage.js`... - ✔ Updating routes file... -Done in 1.02s. -``` - -The page returns jsx telling you where to find it: - -```javascript -// ./web/src/pages/HomePage/HomePage.js - -const HomePage = () => { - return ( -
-

HomePage

-

Find me in ./web/src/pages/HomePage/HomePage.js

-
- ) -} - -export default HomePage -``` - -And the route is added to `Routes.js`: - -```javascript{6} -// ./web/src/Routes.js - -const Routes = () => { - return ( - - - - - ) -} -``` - -Generating a page to show quotes: - -```plaintext -~/redwood-app$ yarn redwood generate page quote {id} -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g page quote {id} - ✔ Generating page files... - ✔ Writing `./web/src/pages/QuotePage/QuotePage.stories.js`... - ✔ Writing `./web/src/pages/QuotePage/QuotePage.test.js`... - ✔ Writing `./web/src/pages/QuotePage/QuotePage.js`... - ✔ Updating routes file... -Done in 1.02s. -``` - -The generated page will get the route parameter as a prop: - -```javascript{5,12,14} -// ./web/src/pages/QuotePage/QuotePage.js - -import { Link, routes } from '@redwoodjs/router' - -const QuotePage = ({ id }) => { - return ( - <> -

QuotePage

-

Find me in "./web/src/pages/QuotePage/QuotePage.js"

-

- My default route is named "quote", link to me with ` - Quote 42` -

-

The parameter passed to me is {id}

- - ) -} - -export default QuotePage -``` - -And the route is added to `Routes.js`, with the route parameter added: - -```javascript{6} -// ./web/src/Routes.js - -const Routes = () => { - return ( - - - - - ) -} -``` - -### generate scaffold - -Generate Pages, SDL, and Services files based on a given DB schema Model. Also accepts ``. - -```terminal -yarn redwood generate scaffold -``` - -A scaffold quickly creates a CRUD for a model by generating the following files and corresponding routes: - -- sdl -- service -- layout -- pages -- cells -- components - -The content of the generated components is different from what you'd get by running them individually. - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `model` | Model to scaffold. You can also use `` to nest files by type at the given path directory (or directories). For example, `redwood g scaffold admin/post` | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | - -**Usage** - -See [Creating a Post Editor](tutorial/chapter2/getting-dynamic.md#creating-a-post-editor). - -**Nesting of Components and Pages** - -By default, redwood will nest the components and pages in a directory named as per the model. For example (where `post` is the model): -`yarn rw g scaffold post` -will output the following files, with the components and pages nested in a `Post` directory: - -```plaintext{9-20} - √ Generating scaffold files... - √ Successfully wrote file `./api/src/graphql/posts.sdl.js` - √ Successfully wrote file `./api/src/services/posts/posts.js` - √ Successfully wrote file `./api/src/services/posts/posts.scenarios.js` - √ Successfully wrote file `./api/src/services/posts/posts.test.js` - √ Successfully wrote file `./web/src/layouts/PostsLayout/PostsLayout.js` - √ Successfully wrote file `./web/src/pages/Post/EditPostPage/EditPostPage.js` - √ Successfully wrote file `./web/src/pages/Post/PostPage/PostPage.js` - √ Successfully wrote file `./web/src/pages/Post/PostsPage/PostsPage.js` - √ Successfully wrote file `./web/src/pages/Post/NewPostPage/NewPostPage.js` - √ Successfully wrote file `./web/src/components/Post/EditPostCell/EditPostCell.js` - √ Successfully wrote file `./web/src/components/Post/Post/Post.js` - √ Successfully wrote file `./web/src/components/Post/PostCell/PostCell.js` - √ Successfully wrote file `./web/src/components/Post/PostForm/PostForm.js` - √ Successfully wrote file `./web/src/components/Post/Posts/Posts.js` - √ Successfully wrote file `./web/src/components/Post/PostsCell/PostsCell.js` - √ Successfully wrote file `./web/src/components/Post/NewPost/NewPost.js` - √ Adding layout import... - √ Adding set import... - √ Adding scaffold routes... - √ Adding scaffold asset imports... -``` - -If it is not desired to nest the components and pages, then redwood provides an option that you can set to disable this for your project. -Add the following in your `redwood.toml` file to disable the nesting of components and pages. - -``` -[generate] - nestScaffoldByModel = false -``` - -Setting the `nestScaffoldByModel = true` will retain the default behavior, but is not required. - -Notes: - -1. The nesting directory is always set to be PascalCase. - -**Namespacing Scaffolds** - -You can namespace your scaffolds by providing ``. The layout, pages, cells, and components will be nested in newly created dir(s). In addition, the nesting folder, based upon the model name, is still applied after the path for components and pages, unless turned off in the `redwood.toml` as described above. For example, given a model `user`, running `yarn redwood generate scaffold admin/user` will nest the layout, pages, and components in a newly created `Admin` directory created for each of the `layouts`, `pages`, and `components` folders: - -```plaintext{9-20} -~/redwood-app$ yarn redwood generate scaffold admin/user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g scaffold admin/user - ✔ Generating scaffold files... - ✔ Successfully wrote file `./api/src/graphql/users.sdl.js` - ✔ Successfully wrote file `./api/src/services/users/users.js` - ✔ Successfully wrote file `./api/src/services/users/users.scenarios.js` - ✔ Successfully wrote file `./api/src/services/users/users.test.js` - ✔ Successfully wrote file `./web/src/layouts/Admin/UsersLayout/UsersLayout.js` - ✔ Successfully wrote file `./web/src/pages/Admin/User/EditUserPage/EditUserPage.js` - ✔ Successfully wrote file `./web/src/pages/Admin/User/UserPage/UserPage.js` - ✔ Successfully wrote file `./web/src/pages/Admin/User/UsersPage/UsersPage.js` - ✔ Successfully wrote file `./web/src/pages/Admin/User/NewUserPage/NewUserPage.js` - ✔ Successfully wrote file `./web/src/components/Admin/User/EditUserCell/EditUserCell.js` - ✔ Successfully wrote file `./web/src/components/Admin/User/User/User.js` - ✔ Successfully wrote file `./web/src/components/Admin/User/UserCell/UserCell.js` - ✔ Successfully wrote file `./web/src/components/Admin/User/UserForm/UserForm.js` - ✔ Successfully wrote file `./web/src/components/Admin/User/Users/Users.js` - ✔ Successfully wrote file `./web/src/components/Admin/User/UsersCell/UsersCell.js` - ✔ Successfully wrote file `./web/src/components/Admin/User/NewUser/NewUser.js` - ✔ Adding layout import... - ✔ Adding set import... - ✔ Adding scaffold routes... - ✔ Adding scaffold asset imports... -Done in 1.21s. -``` - -The routes wrapped in the [`Set`](router.md#sets-of-routes) component with generated layout will be nested too: - -```javascript{6-11} -// ./web/src/Routes.js - -const Routes = () => { - return ( - - - - - - - - - - ) -} -``` - -Notes: - -1. Each directory in the scaffolded path is always set to be PascalCase. -2. The scaffold path may be multiple directories deep. - -**Destroying** - -``` -yarn redwood d scaffold -``` - -Notes: - -1. You can also use `` to destroy files that were generated under a scaffold path. For example, `redwood d scaffold admin/post` -2. The destroy command will remove empty folders along the path, provided they are lower than the folder level of component, layout, page, etc. -3. The destroy scaffold command will also follow the `nestScaffoldbyModel` setting in the `redwood.toml` file. For example, if you have an existing scaffold that you wish to destroy, that does not have the pages and components nested by the model name, you can destroy the scaffold by temporarily setting: - -``` -[generate] - nestScaffoldByModel = false -``` - -### generate sdl - -Generate a GraphQL schema and service object. - -```terminal -yarn redwood generate sdl -``` - -The sdl will inspect your `schema.prisma` and will do its best with relations. Schema to generators isn't one-to-one yet (and might never be). - - - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------ | -| `model` | Model to generate the sdl for | -| `--crud` | Set to `false`, or use `--no-crud`, if you do not want to generate mutations | -| `--force, -f` | Overwrite existing files | -| `--tests` | Generate service test and scenario [default: true] | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | - -> **Note:** The generated sdl will include the `@requireAuth` directive by default to ensure queries and mutations are secure. If your app's queries and mutations are all public, you can set up a custom SDL generator template to apply `@skipAuth` (or a custom validator directive) to suit you application's needs. - -**Regenerating the SDL** - -Often, as you iterate on your data model, you may add, remove, or rename fields. You still want Redwood to update the generated SDL and service files for those updates because it saves time not having to make those changes manually. - -But, since the `generate` command prevents you from overwriting files accidentally, you use the `--force` option -- but a `force` will reset any test and scenarios you may have written which you don't want to lose. - -In that case, you can run the following to "regenerate" **just** the SDL file and leave your tests and scenarios intact and not lose your hard work. - -``` -yarn redwood g sdl --force --no-tests -``` - -**Example** - -```terminal -~/redwood-app$ yarn redwood generate sdl user --force --no-tests -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g sdl user - ✔ Generating SDL files... - ✔ Writing `./api/src/graphql/users.sdl.js`... - ✔ Writing `./api/src/services/users/users.js`... -Done in 1.04s. -``` - -**Destroying** - -``` -yarn redwood d sdl -``` - -**Example** - -Generating a user sdl: - -```terminal -~/redwood-app$ yarn redwood generate sdl user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g sdl user - ✔ Generating SDL files... - ✔ Writing `./api/src/graphql/users.sdl.js`... - ✔ Writing `./api/src/services/users/users.scenarios.js`... - ✔ Writing `./api/src/services/users/users.test.js`... - ✔ Writing `./api/src/services/users/users.js`... -Done in 1.04s. -``` - -The generated sdl defines a corresponding type, query, create/update inputs, and any mutations. To prevent defining mutations, add the `--no-crud` option. - -```javascript -// ./api/src/graphql/users.sdl.js - -export const schema = gql` - type User { - id: Int! - email: String! - name: String - } - - type Query { - users: [User!]! @requireAuth - } - - input CreateUserInput { - email: String! - name: String - } - - input UpdateUserInput { - email: String - name: String - } - - type Mutation { - createUser(input: CreateUserInput!): User! @requireAuth - updateUser(id: Int!, input: UpdateUserInput!): User! @requireAuth - deleteUser(id: Int!): User! @requireAuth - } -` -``` - -The services file fulfills the query. If the `--no-crud` option is added, this file will be less complex. - -```javascript -// ./api/src/services/users/users.js - -import { db } from 'src/lib/db' - -export const users = () => { - return db.user.findMany() -} -``` - -For a model with a relation, the field will be listed in the sdl: - -```javascript{8} -// ./api/src/graphql/users.sdl.js - -export const schema = gql` - type User { - id: Int! - email: String! - name: String - profile: Profile - } - - type Query { - users: [User!]! @requireAuth - } - - input CreateUserInput { - email: String! - name: String - } - - input UpdateUserInput { - email: String - name: String - } - - type Mutation { - createUser(input: CreateUserInput!): User! @requireAuth - updateUser(id: Int!, input: UpdateUserInput!): User! @requireAuth - deleteUser(id: Int!): User! @requireAuth - } -` -``` - -And the service will export an object with the relation as a property: - -```javascript{9-13} -// ./api/src/services/users/users.js - -import { db } from 'src/lib/db' - -export const users = () => { - return db.user.findMany() -} - -export const User = { - profile: (_obj, { root }) => { - db.user.findUnique({ where: { id: root.id } }).profile(), - } -} -``` - -### generate secret - -Generate a secret key using a cryptographically-secure source of entropy. Commonly used when setting up dbAuth. - -| Arguments & Options | Description | -| :------------------ | :------------------------------------------------- | -| `--raw` | Print just the key, without any informational text | - -**Usage** - -Using the `--raw` option you can easily append a secret key to your .env file, like so: - -``` -echo "SESSION_SECRET=$(yarn --silent rw g secret --raw)" >> .env -``` - -### generate service - -Generate a service component. - -```terminal -yarn redwood generate service -``` - -Services are where Redwood puts its business logic. They can be used by your GraphQL API or any other place in your backend code. See [How Redwood Works with Data](tutorial/chapter2/side-quest.md). - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------ | -| `name` | Name of the service | -| `--force, -f` | Overwrite existing files | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | -| `--tests` | Generate test and scenario files [default: true] | - - -**Destroying** - -``` -yarn redwood d service -``` - -**Example** - -Generating a user service: - -```terminal -~/redwood-app$ yarn redwood generate service user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood g service user - ✔ Generating service files... - ✔ Writing `./api/src/services/users/users.scenarios.js`... - ✔ Writing `./api/src/services/users/users.test.js`... - ✔ Writing `./api/src/services/users/users.js`... -Done in 1.02s. -``` - -The generated service component will export a `findMany` query: - -```javascript -// ./api/src/services/users/users.js - -import { db } from 'src/lib/db' - -export const users = () => { - return db.user.findMany() -} -``` - -### generate types - -Generates supplementary code (project types) - -```terminal -yarn redwood generate types -``` - -**Usage** - -``` -~/redwood-app$ yarn redwood generate types -yarn run v1.22.10 -$ /redwood-app/node_modules/.bin/redwood g types -$ /redwood-app/node_modules/.bin/rw-gen - -Generating... - -- .redwood/schema.graphql -- .redwood/types/mirror/api/src/services/posts/index.d.ts -- .redwood/types/mirror/web/src/components/BlogPost/index.d.ts -- .redwood/types/mirror/web/src/layouts/BlogLayout/index.d.ts -... -- .redwood/types/mirror/web/src/components/Post/PostsCell/index.d.ts -- .redwood/types/includes/web-routesPages.d.ts -- .redwood/types/includes/all-currentUser.d.ts -- .redwood/types/includes/web-routerRoutes.d.ts -- .redwood/types/includes/api-globImports.d.ts -- .redwood/types/includes/api-globalContext.d.ts -- .redwood/types/includes/api-scenarios.d.ts -- api/types/graphql.d.ts -- web/types/graphql.d.ts - -... and done. -``` - -### generate script - -Generates an arbitrary Node.js script in `./scripts/` that can be used with `redwood execute` command later. - -| Arguments & Options | Description | -| -------------------- | ------------------------------------------------------------------------------------ | -| `name` | Name of the service | -| `--typescript, --ts` | Generate TypeScript files Enabled by default if we detect your project is TypeScript | - -Scripts have access to services and libraries used in your project. Some examples of how this can be useful: - -- create special database seed scripts for different scenarios -- sync products and prices from your payment provider -- running cleanup jobs on a regular basis e.g. delete stale/expired data -- sync data between platforms e.g. email from your db to your email marketing platform - -**Usage** - -``` -❯ yarn rw g script syncStripeProducts - - ✔ Generating script file... - ✔ Successfully wrote file `./scripts/syncStripeProducts.ts` - ✔ Next steps... - - After modifying your script, you can invoke it like: - - yarn rw exec syncStripeProducts - - yarn rw exec syncStripeProducts --param1 true -``` - -## info - -Print your system environment information. - -```terminal -yarn redwood info -``` - -This command's primarily intended for getting information others might need to know to help you debug: - -```terminal -~/redwood-app$ yarn redwood info -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/redwood info - - System: - OS: Linux 5.4 Ubuntu 20.04 LTS (Focal Fossa) - Shell: 5.0.16 - /usr/bin/bash - Binaries: - Node: 13.12.0 - /tmp/yarn--1589998865777-0.9683603763419713/node - Yarn: 1.22.4 - /tmp/yarn--1589998865777-0.9683603763419713/yarn - Browsers: - Chrome: 78.0.3904.108 - Firefox: 76.0.1 - npmPackages: - @redwoodjs/core: ^0.7.0-rc.3 => 0.7.0-rc.3 - -Done in 1.98s. -``` - -## lint - -Lint your files. - -```terminal -yarn redwood lint -``` - -[Our ESLint configuration](https://github.com/redwoodjs/redwood/blob/master/packages/eslint-config/index.js) is a mix of [ESLint's recommended rules](https://eslint.org/docs/rules/), [React's recommended rules](https://www.npmjs.com/package/eslint-plugin-react#list-of-supported-rules), and a bit of our own stylistic flair: - -- no semicolons -- comma dangle when multiline -- single quotes -- always use parenthesis around arrow functions -- enforced import sorting - -| Option | Description | -| :------ | :---------------- | -| `--fix` | Try to fix errors | - -## prisma - -Run Prisma CLI with experimental features. - -``` -yarn redwood prisma -``` - -Redwood's `prisma` command is a lightweight wrapper around the Prisma CLI. It's the primary way you interact with your database. - -> **What do you mean it's a lightweight wrapper?** -> -> By lightweight wrapper, we mean that we're handling some flags under the hood for you. -> You can use the Prisma CLI directly (`yarn prisma`), but letting Redwood act as a proxy (`yarn redwood prisma`) saves you a lot of keystrokes. -> For example, Redwood adds the `--preview-feature` and `--schema=api/db/schema.prisma` flags automatically. -> -> If you want to know exactly what `yarn redwood prisma ` runs, which flags it's passing, etc., it's right at the top: -> -> ```sh{3} -> $ yarn redwood prisma migrate dev -> yarn run v1.22.10 -> $ ~/redwood-app/node_modules/.bin/redwood prisma migrate dev -> Running prisma cli: -> yarn prisma migrate dev --schema "~/redwood-app/api/db/schema.prisma" -> ... -> ``` - -Since `yarn redwood prisma` is just an entry point into all the database commands that the Prisma CLI has to offer, we won't try to provide an exhaustive reference of everything you can do with it here. Instead what we'll do is focus on some of the most common commands; those that you'll be running on a regular basis, and how they fit into Redwood's workflows. - -For the complete list of commands, see the [Prisma CLI Reference](https://www.prisma.io/docs/reference/api-reference/command-reference). It's the authority. - -Along with the CLI reference, bookmark Prisma's [Migration Flows](https://www.prisma.io/docs/concepts/components/prisma-migrate/prisma-migrate-flows) doc—it'll prove to be an invaluable resource for understanding `yarn redwood prisma migrate`. - -| Command | Description | -| :------------------ | :----------------------------------------------------------- | -| `db ` | Manage your database schema and lifecycle during development | -| `generate` | Generate artifacts (e.g. Prisma Client) | -| `migrate ` | Update the database schema with migrations | - -### prisma db - -Manage your database schema and lifecycle during development. - -``` -yarn redwood prisma db -``` - -The `prisma db` namespace contains commands that operate directly against the database. - -#### prisma db pull - -Pull the schema from an existing database, updating the Prisma schema. - -> 👉 Quick link to the [Prisma CLI Reference](https://www.prisma.io/docs/reference/api-reference/command-reference#db-pull). - -``` -yarn redwood prisma db pull -``` - -This command, formerly `introspect`, connects to your database and adds Prisma models to your Prisma schema that reflect the current database schema. - -> Warning: The command will Overwrite the current schema.prisma file with the new schema. Any manual changes or customization will be lost. Be sure to back up your current schema.prisma file before running `db pull` if it contains important modifications. - -#### prisma db push - -Push the state from your Prisma schema to your database. - -> 👉 Quick link to the [Prisma CLI Reference](https://www.prisma.io/docs/reference/api-reference/command-reference#db-push). - -``` -yarn redwood prisma db push -``` - -This is your go-to command for prototyping changes to your Prisma schema (`schema.prisma`). -Prior to to `yarn redwood prisma db push`, there wasn't a great way to try out changes to your Prisma schema without creating a migration. -This command fills the void by "pushing" your `schema.prisma` file to your database without creating a migration. You don't even have to run `yarn redwood prisma generate` afterward—it's all taken care of for you, making it ideal for iterative development. - -#### prisma db seed - -Seed your database. - -> 👉 Quick link to the [Prisma CLI Reference](https://www.prisma.io/docs/reference/api-reference/command-reference#db-seed-preview). - -``` -yarn redwood prisma db seed -``` - -This command seeds your database by running your project's `seed.js|ts` file which you can find in your `scripts` directory. - -Prisma's got a great [seeding guide](https://www.prisma.io/docs/guides/prisma-guides/seed-database) that covers both the concepts and the nuts and bolts. - -> **Important:** Prisma Migrate also triggers seeding in the following scenarios: -> -> - you manually run the `yarn redwood prisma migrate reset` command -> - the database is reset interactively in the context of using `yarn redwood prisma migrate dev`—for example, as a result of migration history conflicts or database schema drift -> -> If you want to use `yarn redwood prisma migrate dev` or `yarn redwood prisma migrate reset` without seeding, you can pass the `--skip-seed` flag. - -While having a great seed might not be all that important at the start, as soon as you start collaborating with others, it becomes vital. - -**How does seeding actually work?** - -If you look at your project's `package.json` file, you'll notice a `prisma` section: - -```json - "prisma": { - "seed": "yarn rw exec seed" - }, -``` - -Prisma runs any command found in the `seed` setting when seeding via `yarn rw prisma db seed` or `yarn rw prisma migrate reset`. -Here we're using the Redwood [`exec` cli command](#exec) that runs a script. - -If you wanted to seed your database using a different method (like `psql` and an `.sql` script), you can do so by changing the "seed" script command. - -**More About Seeding** - -In addition, you can [code along with Ryan Chenkie](https://www.youtube.com/watch?v=2LwTUIqjbPo), and learn how libraries like [faker](https://www.npmjs.com/package/faker) can help you create a large, realistic database fast, especially in tandem with Prisma's [createMany](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#createmany). - - - - - - - - - - - -**Log Formatting** - -If you use the Redwood Logger as part of your seed script, you can pipe the command to the LogFormatter to output prettified logs. - -For example, if your `scripts.seed.js` imports the `logger`: - -```js -// scripts/seed.js -import { db } from 'api/src/lib/db' -import { logger } from 'api/src/lib/logger' - -export default async () => { - try { - const posts = [ - { - title: 'Welcome to the blog!', - body: "I'm baby single- origin coffee kickstarter lo.", - }, - { - title: 'A little more about me', - body: 'Raclette shoreditch before they sold out lyft.', - }, - { - title: 'What is the meaning of life?', - body: 'Meh waistcoat succulents umami asymmetrical, hoodie post-ironic paleo chillwave tote bag.', - }, - ] - - Promise.all( - posts.map(async (post) => { - const newPost = await db.post.create({ - data: { title: post.title, body: post.body }, - }) - - logger.debug({ data: newPost }, 'Added post') - }) - ) - } catch (error) { - logger.error(error) - } -} -``` - -You can pipe the script output to the formatter: - -```bash -yarn rw prisma db seed | yarn rw-log-formatter -``` - -> Note: Just be sure to set `data` attribute, so the formatter recognizes the content. -> For example: `logger.debug({ data: newPost }, 'Added post')` - -### prisma migrate - -Update the database schema with migrations. - -> 👉 Quick link to the [Prisma Concepts](https://www.prisma.io/docs/concepts/components/prisma-migrate). - -``` -yarn redwood prisma migrate -``` - -As a database toolkit, Prisma strives to be as holistic as possible. Prisma Migrate lets you use Prisma schema to make changes to your database declaratively, all while keeping things deterministic and fully customizable by generating the migration steps in a simple, familiar format: SQL. - -Since migrate generates plain SQL files, you can edit those SQL files before applying the migration using `yarn redwood prisma migrate --create-only`. This creates the migration based on the changes in the Prisma schema, but doesn't apply it, giving you the chance to go in and make any modifications you want. [Daniel Norman's tour of Prisma Migrate](https://www.youtube.com/watch?v=0LKhksstrfg) demonstrates this and more to great effect. - -Prisma Migrate has separate commands for applying migrations based on whether you're in dev or in production. The Prisma [Migration flows](https://www.prisma.io/docs/concepts/components/prisma-migrate/prisma-migrate-flows) goes over the difference between these workflows in more detail. - -#### prisma migrate dev - -Create a migration from changes in Prisma schema, apply it to the database, trigger generators (e.g. Prisma Client). - -> 👉 Quick link to the [Prisma CLI Reference](https://www.prisma.io/docs/reference/api-reference/command-reference#migrate-dev). - -``` -yarn redwood prisma migrate dev -``` - - - - - - - - - - - -#### prisma migrate deploy - -Apply pending migrations to update the database schema in production/staging. - -> 👉 Quick link to the [Prisma CLI Reference](https://www.prisma.io/docs/reference/api-reference/command-reference#migrate-deploy). - -``` -yarn redwood prisma migrate deploy -``` - -#### prisma migrate reset - -This command deletes and recreates the database, or performs a "soft reset" by removing all data, tables, indexes, and other artifacts. - -It'll also re-seed your database by automatically running the `db seed` command. See [prisma db seed](#prisma-db-seed). - -> **_Important:_** For use in development environments only - -## record - -> This command is experimental and its behavior may change. - -Commands for working with RedwoodRecord. - -### record init - -Parses `schema.prisma` and caches the datamodel as JSON. Reads relationships between models and adds some configuration in `api/src/models/index.js`. - -``` -yarn rw record init -``` - -## redwood-tools (alias rwt) - -Redwood's companion CLI development tool. You'll be using this if you're contributing to Redwood. See [Contributing](https://github.com/redwoodjs/redwood/blob/main/CONTRIBUTING.md#cli-reference-redwood-tools) in the Redwood repo. - -## setup - -Initialize configuration and integrate third-party libraries effortlessly. - -``` -yarn redwood setup -``` - -| Commands | Description | -| ------------------ | ------------------------------------------------------------------------------------------ | -| `auth` | Set up auth configuration for a provider | -| `custom-web-index` | Set up an `index.js` file, so you can customize how Redwood web is mounted in your browser | -| `deploy` | Set up a deployment configuration for a provider | -| `generator` | Copy default Redwood generator templates locally for customization | -| `i18n` | Set up i18n | -| `tsconfig` | Add relevant tsconfig so you can start using TypeScript | -| `ui` | Set up a UI design or style library | -| `webpack` | Set up a webpack config file in your project so you can add custom config | - -### setup auth - -Integrate an auth provider. - -``` -yarn redwood setup auth -``` - -| Arguments & Options | Description | -| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `provider` | Auth provider to configure. Choices are `auth0`, `azureActiveDirectory`, `clerk`, `dbAuth`, `ethereum`, `firebase`, `goTrue`, `magicLink`, `netlify`, `nhost`, and `supabase` | -| `--force, -f` | Overwrite existing configuration | - -**Usage** - -See [Authentication](authentication.md). - -### setup custom-web-index - -Redwood automatically mounts your `` to the DOM, but if you want to customize how that happens, you can use this setup command to generate an `index.js` file in `web/src`. - -``` -yarn redwood setup custom-web-index -``` - -| Arguments & Options | Description | -| :------------------ | :----------------------- | -| `--force, -f` | Overwrite existing files | - -### setup generator - -Copies a given generator's template files to your local app for customization. The next time you generate that type again, it will use your custom template instead of Redwood's default. - -``` -yarn rw setup generator -``` - -| Arguments & Options | Description | -| :------------------ | :------------------------------------------------------------ | -| `name` | Name of the generator template(s) to copy (see help for list) | -| `--force, -f` | Overwrite existing copied template files | - -**Usage** - -If you wanted to customize the page generator template, run the command: - -``` -yarn rw setup generator page -``` - -And then check `web/generators/page` for the page, storybook and test template files. You don't need to keep all of these templates—you could customize just `page.tsx.template` and delete the others and they would still be generated, but using the default Redwood templates. - -The only exception to this rule is the scaffold templates. You'll get four directories, `assets`, `components`, `layouts` and `pages`. If you want to customize any one of the templates in those directories, you will need to keep all the other files inside of that same directory, even if you make no changes besides the one you care about. (This is due to the way the scaffold looks up its template files.) For example, if you wanted to customize only the index page of the scaffold (the one that lists all available records in the database) you would edit `web/generators/scaffold/pages/NamesPage.tsx.template` and keep the other pages in that directory. You _could_ delete the other three directories (`assets`, `components`, `layouts`) if you don't need to customize them. - -**Name Variants** - -Your template will receive the provided `name` in a number of different variations. - -For example, given the name `fooBar` your template will receive the following _variables_ with the given _values_ - -| Variable | Value | -| :------------------------ | :------------ | -| `pascalName` | `FooBar` | -| `camelName` | `fooBar` | -| `singularPascalName` | `FooBar` | -| `pluralPascalName` | `FooBars` | -| `singularCamelName` | `fooBar` | -| `pluralCamelName` | `fooBars` | -| `singularParamName` | `foo-bar` | -| `pluralParamName` | `foo-bars` | -| `singularConstantName` | `FOO_BAR` | -| `pluralConstantName` | `FOO_BARS` | - -**Example** - -Copying the cell generator templates: - -```terminal -~/redwood-app$ yarn rw setup generator cell -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/rw setup generator cell - ✔ Copying generator templates... - ✔ Wrote templates to /web/generators/cell -✨ Done in 2.33s. -``` - -### setup deploy (config) - -Set up a deployment configuration. - -``` -yarn redwood setup deploy -``` - -| Arguments & Options | Description | -| :------------------ | :---------------------------------------------------------------------------------------------------- | -| `provider` | Deploy provider to configure. Choices are `aws-serverless`, `netlify`, `render`, or `vercel` | -| `--database, -d` | Database deployment for Render only [choices: "none", "postgresql", "sqlite"] [default: "postgresql"] | -| `--force, -f` | Overwrite existing configuration [default: false] | - -#### setup deploy netlify - -When configuring Netlify deployment, the `setup deploy netlify` command generates a `netlify.toml` [configuration file](https://docs.netlify.com/configure-builds/file-based-configuration/) with the defaults needed to build and deploy a RedwoodJS site on Netlify. - -The `netlify.toml` file is a configuration file that specifies how Netlify builds and deploys your site — including redirects, branch and context-specific settings, and more. - -This configuration file also defines the settings needed for [Netlify Dev](https://docs.netlify.com/configure-builds/file-based-configuration/#netlify-dev) to detect that your site uses the RedwoodJS framework. Netlify Dev serves your RedwoodJS app as if it runs on the Netlify platform and can serve functions, handle Netlify [headers](https://docs.netlify.com/configure-builds/file-based-configuration/#headers) and [redirects](https://docs.netlify.com/configure-builds/file-based-configuration/#redirects). - -Netlify Dev can also create a tunnel from your local development server that allows you to share and collaborate with others using `netlify dev --live`. - -``` -// See: netlify.toml -// ... -[dev] - # To use [Netlify Dev](https://www.netlify.com/products/dev/), - # install netlify-cli from https://docs.netlify.com/cli/get-started/#installation - # and then use netlify link https://docs.netlify.com/cli/get-started/#link-and-unlink-sites - # to connect your local project to a site already on Netlify - # then run netlify dev and our app will be accessible on the port specified below - framework = "redwoodjs" - # Set targetPort to the [web] side port as defined in redwood.toml - targetPort = 8910 - # Point your browser to this port to access your RedwoodJS app - port = 8888 -``` - -In order to use [Netlify Dev](https://www.netlify.com/products/dev/) you need to: - -- install the latest [netlify-cli](https://docs.netlify.com/cli/get-started/#installation) -- use [netlify link](https://docs.netlify.com/cli/get-started/#link-and-unlink-sites) to connect to your Netlify site -- ensure that the `targetPort` matches the [web] side port in `redwood.toml` -- run `netlify dev` and your site will be served on the specified `port` (e.g., 8888) -- if you wish to share your local server with others, you can run `netlify dev --live` - -> Note: To detect the RedwoodJS framework, please use netlify-cli v3.34.0 or greater. - -### setup tsconfig - -Add a `tsconfig.json` to both the web and api sides so you can start using [TypeScript](typescript.md). - -``` -yarn redwood setup tsconfig -``` - -| Arguments & Options | Description | -| :------------------ | :----------------------- | -| `--force, -f` | Overwrite existing files | - -### setup ui - -Set up a UI design or style library. Right now the choices are [Chakra UI](https://chakra-ui.com/) and [TailwindCSS](https://tailwindcss.com/). - -``` -yarn rw setup ui -``` - -| Arguments & Options | Description | -| :------------------ | :-------------------------------------------------------------- | -| `library` | Library to configure. Choices are `chakra-ui` and `tailwindcss` | -| `--force, -f` | Overwrite existing configuration | - -## storybook - -Starts Storybook locally - -```terminal -yarn redwood storybook -``` - -[Storybook](https://storybook.js.org/docs/react/get-started/introduction) is a tool for UI development that allows you to develop your components in isolation, away from all the conflated cruft of your real app. - -> "Props in, views out! Make it simple to reason about." - -RedwoodJS supports Storybook by creating stories when generating cells, components, layouts and pages. You can then use these to describe how to render that UI component with representative data. - -| Arguments & Options | Description | -| :------------------ | :------------------------------------------------ | -| `--open` | Open Storybook in your browser on start | -| `--build` | Build Storybook | -| `--port` | Which port to run Storybook on (defaults to 7910) | - -## test - -Run Jest tests for api and web. - -```terminal -yarn redwood test [side..] -``` - -| Arguments & Options | Description | -| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `sides or filter` | Which side(s) to test, and/or a regular expression to match against your test files to filter by | -| `--help` | Show help | -| `--version` | Show version number | -| `--watch` | Run tests related to changed files based on hg/git (uncommitted files). Specify the name or path to a file to focus on a specific set of tests [default: true] | -| `--watchAll` | Run all tests | -| `--collectCoverage` | Show test coverage summary and output info to `coverage` directory in project root. See this directory for an .html coverage report | -| `--clearCache` | Delete the Jest cache directory and exit without running tests | -| `--db-push` | Syncs the test database with your Prisma schema without requiring a migration. It creates a test database if it doesn't already exist [default: true]. This flag is ignored if your project doesn't have an `api` side. [👉 More details](#prisma-db-push). | - -> **Note** all other flags are passed onto the jest cli. So for example if you wanted to update your snapshots you can pass the `-u` flag - -## type-check - -Runs a TypeScript compiler check on both the api and the web sides. - -```terminal -yarn redwood type-check [side] -``` - -| Arguments & Options | Description | -| ------------------- | ------------------------------------------------------------------------------ | -| `side` | Which side(s) to run. Choices are `api` and `web`. Defaults to `api` and `web` | - -**Usage** - -See [Running Type Checks](typescript.md#running-type-checks). - -## serve - -Runs a server that serves both the api and the web sides. - -```terminal -yarn redwood serve [side] -``` - -> You should run `yarn rw build` before running this command to make sure all the static assets that will be served have been built. - -`yarn rw serve` is useful for debugging locally or for self-hosting—deploying a single server into a serverful environment. Since both the api and the web sides run in the same server, CORS isn't a problem. - -| Arguments & Options | Description | -| ------------------- | ------------------------------------------------------------------------------ | -| `side` | Which side(s) to run. Choices are `api` and `web`. Defaults to `api` and `web` | -| `--port` | What port should the server run on [default: 8911] | -| `--socket` | The socket the server should run. This takes precedence over port | - -### serve api - -Runs a server that only serves the api side. - -``` -yarn rw serve api -``` - -This command uses `apiUrl` in your `redwood.toml`. Use this command if you want to run just the api side on a server (e.g. running on Render). - -| Arguments & Options | Description | -| ------------------- | ----------------------------------------------------------------- | -| `--port` | What port should the server run on [default: 8911] | -| `--socket` | The socket the server should run. This takes precedence over port | -| `--apiRootPath` | The root path where your api functions are served | - -For the full list of Server Configuration settings, see [this documentation](app-configuration-redwood-toml.md#api). -If you want to format your log output, you can pipe the command to the Redwood LogFormatter: - -``` -yarn rw serve api | yarn rw-log-formatter -``` - -### serve web - -Runs a server that only serves the web side. - -``` -yarn rw serve web -``` - -This command serves the contents in `web/dist`. Use this command if you're debugging (e.g. great for debugging prerender) or if you want to run your api and web sides on separate servers, which is often considered a best practice for scalability (since your api side likely has much higher scaling requirements). - -> **But shouldn't I use nginx and/or equivalent technology to serve static files?** -> -> Probably, but it can be a challenge to setup when you just want something running quickly! - -| Arguments & Options | Description | -| ------------------- | ------------------------------------------------------------------------------------- | -| `--port` | What port should the server run on [default: 8911] | -| `--socket` | The socket the server should run. This takes precedence over port | -| `--apiHost` | Forwards requests from the `apiUrl` (defined in `redwood.toml`) to the specified host | - -If you want to format your log output, you can pipe the command to the Redwood LogFormatter: - -``` -yarn rw serve web | yarn rw-log-formatter -``` - -## upgrade - -Upgrade all `@redwoodjs` packages via an interactive CLI. - -```terminal -yarn redwood upgrade -``` - -This command does all the heavy-lifting of upgrading to a new release for you. - -Besides upgrading to a new stable release, you can use this command to upgrade to either of our unstable releases, `canary` and `rc`, or you can upgrade to a specific release version. - -A canary release is published to npm every time a PR is merged to the `main` branch, and when we're getting close to a new release, we publish release candidates. - -| Option | Description | -| :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--dry-run, -d` | Check for outdated packages without upgrading | -| `--tag, -t` | Choices are "canary", "rc", or a specific version (e.g. "0.19.3"). WARNING: Unstable releases in the case of "canary" and "rc", which will force upgrade packages to the most recent release of the specified tag. | - -**Example** - -Upgrade to the most recent canary: - -```terminal -yarn redwood upgrade -t canary -``` - -Upgrade to a specific version: - -```terminal -yarn redwood upgrade -t 0.19.3 -``` diff --git a/docs/versioned_docs/version-1.0/connection-pooling.md b/docs/versioned_docs/version-1.0/connection-pooling.md deleted file mode 100644 index c6bf7d300212..000000000000 --- a/docs/versioned_docs/version-1.0/connection-pooling.md +++ /dev/null @@ -1,75 +0,0 @@ -# Connection Pooling - -> ⚠ **Work in Progress** ⚠️ -> -> There's more to document here. In the meantime, you can check our [community forum](https://community.redwoodjs.com/search?q=connection%20pooling) for answers. -> -> Want to contribute? Redwood welcomes contributions and loves helping people become contributors. -> You can edit this doc [here](https://github.com/redwoodjs/redwoodjs.com/blob/main/docs/connectionPooling.md). -> If you have any questions, just ask for help! We're active on the [forums](https://community.redwoodjs.com/c/contributing/9) and on [discord](https://discord.com/channels/679514959968993311/747258086569541703). - -Production Redwood apps should enable connection pooling in order to properly scale with your Serverless functions. -## Prisma Pooling with PgBouncer - -PgBouncer holds a connection pool to the database and proxies incoming client connections by sitting between Prisma Client and the database. This reduces the number of processes a database has to handle at any given time. PgBouncer passes on a limited number of connections to the database and queues additional connections for delivery when space becomes available. - - -To use Prisma Client with PgBouncer from a serverless function, add the `?pgbouncer=true` flag to the PostgreSQL connection URL: - -``` -postgresql://USER:PASSWORD@HOST:PORT/DATABASE?pgbouncer=true -``` - -Typically, your PgBouncer port will be 6543 which is different than the Postgres default of 5432. - -> Note that since Prisma Migrate uses database transactions to check out the current state of the database and the migrations table, if you attempt to run Prisma Migrate commands in any environment that uses PgBouncer for connection pooling, you might see an error. -> -> To work around this issue, you must connect directly to the database rather than going through PgBouncer when migrating. - -For more information on Prisma and PgBouncer, please refer to Prisma's Guide on [Configuring Prisma Client with PgBouncer](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management/configure-pg-bouncer). - -## Supabase - -For Postgres running on [Supabase](https://supabase.io) see: [PgBouncer is now available in Supabase](https://supabase.io/blog/2021/04/02/supabase-pgbouncer#using-connection-pooling-in-supabase). - -All new Supabase projects include connection pooling using [PgBouncer](https://www.pgbouncer.org/). - -We recommend that you connect to your Supabase Postgres instance using SSL which you can do by setting `sslmode` to `require` on the connection string: - -``` -// not pooled typically uses port 5432 -postgresql://postgres:mydb.supabase.co:5432/postgres?sslmode=require -// pooled typically uses port 6543 -postgresql://postgres:mydb.supabase.co:6543/postgres?sslmode=require&pgbouncer=true -``` - -## Heroku -For Postgres, see [Postgres Connection Pooling](https://devcenter.heroku.com/articles/postgres-connection-pooling). - -Heroku does not officially support MySQL. - - -## Digital Ocean -For Postgres, see [How to Manage Connection Pools](https://www.digitalocean.com/docs/databases/postgresql/how-to/manage-connection-pools) - -Connection Pooling for MySQL is not yet supported. - -## AWS -Use [Amazon RDS Proxy](https://aws.amazon.com/rds/proxy) for MySQL or PostgreSQL. - -From the [AWS Docs](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html#rds-proxy.limitations): ->Your RDS Proxy must be in the same VPC as the database. The proxy can't be publicly accessible. - -Because of this limitation, with out-of-the-box configuration, you can only use RDS Proxy if you're deploying your Lambda Functions to the same AWS account. Alternatively, you can use RDS directly, but you might require larger instances to handle your production traffic and the number of concurrent connections. - - -## Why Connection Pooling? - -Relational databases have a maximum number of concurrent client connections. - -* Postgres allows 100 by default -* MySQL allows 151 by default - -In a traditional server environment, you would need a large amount of traffic (and therefore web servers) to exhaust these connections, since each web server instance typically leverages a single connection. - -In a Serverless environment, each function connects directly to the database, which can exhaust limits quickly. To prevent connection errors, you should add a connection pooling service in front of your database. Think of it as a load balancer. diff --git a/docs/versioned_docs/version-1.0/contributing-overview.md b/docs/versioned_docs/version-1.0/contributing-overview.md deleted file mode 100644 index fa0c63a2bc04..000000000000 --- a/docs/versioned_docs/version-1.0/contributing-overview.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -slug: contributing ---- - -# Contributing: Overview and Orientation - -Love Redwood and want to get involved? You’re in the right place and in good company! As of this writing, there are more than [250 contributors](https://github.com/redwoodjs/redwood/blob/main/README.md#contributors) who have helped make Redwood awesome by contributing code and documentation. This doesn't include all those who participate in the vibrant, helpful, and encouraging Forums and Discord, which are both great places to get started if you have any questions. - -There are several ways you can contribute to Redwood: - -- join the [community Forums](https://community.redwoodjs.com/) and [Discord server](https://discord.gg/jjSYEQd) — encourage and help others 🙌 -- [triage issues on the repo](https://github.com/redwoodjs/redwood/issues) and [review PRs](https://github.com/redwoodjs/redwood/pulls) 🩺 -- write and edit [docs](#contributing-docs) ✍️ -- and of course, write code! 👩‍💻 - -_Before interacting with the Redwood community, please read and understand our [Code of Conduct](https://github.com/redwoodjs/redwood/blob/main/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct)._ - -> ⚡️ **Quick Links** -> -> There are several contributing docs and references, each covering specific topics: -> -> 1. 🧭 **Overview and Orientation** (👈 you are here) -> 2. 📓 [Reference: Contributing to the Framework Packages](https://github.com/redwoodjs/redwood/blob/main/CONTRIBUTING.md) -> 3. 🪜 [Step-by-step Walkthrough](contributing-walkthrough.md) (including Video Recording) -> 4. 📈 [Current Project Status: v1 Release Board](https://github.com/orgs/redwoodjs/projects/6) -> 5. 🤔 What should I work on? -> - ["Help Wanted" v1 Triage Board](https://redwoodjs.com/good-first-issue) -> - [Discovery Process and Open Issues](#what-should-i-work-on) - -## The Characteristics of a Contributor -More than committing code, contributing is about human collaboration and relationship. Our community mantra is **“By helping each other be successful with Redwood, we make the Redwood project successful.”** We have a specific vision for the effect this project and community will have on you — it should give you superpowers to build+create, progress in skills, and help advance your career. - -So who do you need to become to achieve this? Specifically, what characteristics, skills, and capabilities will you need to cultivate through practice? Here are our suggestions: -- Empathy -- Gratitude -- Generosity - -All of these are applicable in relation to both others and yourself. The goal of putting them into practice is to create trust that will be a catalyst for risk-taking (another word to describe this process is “learning”!). These are the ingredients necessary for productive, positive collaboration. - -And you thought all this was just about opening a PR 🤣 Yes, it’s a super rewarding experience. But that’s just the beginning! - -## What should I work on? -Even if you know the mechanics, it’s hard to get started without a starting place. Our best advice is this — dive into the Redwood Tutorial, read the docs, and build your own experiment with Redwood. Along the way, you’ll find typos, out-of-date (or missing) documentation, code that could work better, or even opportunities for improving and adding features. You’ll be engaging in the Forums and Chat and developing a feel for priorities and needs. This way, you’ll naturally follow your own interests and sooner than later intersect “things you’re interested in” + “ways to help improve Redwood”. - -There are other more direct ways to get started as well, which are outlined below. - -### Roadmap to Redwood v1: Project Boards and GitHub Issues -Over the next few months, our focus is to achieve a v1.0.0 release of Redwood. You can read Tom’s important announcement about the v1 release candidate process [via this forum post](https://community.redwoodjs.com/t/what-the-1-0-release-candidate-phase-means-and-when-1-0-will-drop/2604). - -> **What a v1 release candidate means:** -> -> 1. all core features are complete and -> 2. we are done making breaking changes -> -> During the release candidate cycle, we are completing all remaining tasks necessary to publish v1.0.0 GA. - -The Redwood Core Team is working publicly — progress is updated daily on the [Release Project Board](https://github.com/orgs/redwoodjs/projects/6). There’s also a Triage Board, including an important tab view of Issues that are priorities but need community help. -- 👉 **Start here**: [v1 Help Wanted tab on the Triage Project Board](https://github.com/orgs/redwoodjs/projects/4) (sorted by difficulty) - - -Eventually, all this leads you back to Redwood’s GitHub Issues page. Here you’ll find open items that need help, which are organized by labels. There are four labels helpful for contributing: -1. [Good First Issue](https://github.com/redwoodjs/redwood/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22): these items are more likely to be an accessible entry point to the Framework. It’s less about skill level and more about focused scope. -2. [Help Wanted](https://github.com/redwoodjs/redwood/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22): these items especially need contribution help from the community. -3. [v1 Priority](https://github.com/redwoodjs/redwood/issues?q=is%3Aissue+is%3Aopen+label%3Av1%2Fpriority+): to reach Redwood v1.0.0, we need to close all Issues with this label. -4. [Bugs 🐛](https://github.com/redwoodjs/redwood/issues?q=is%3Aissue+is%3Aopen+label%3Abug%2Fconfirmed): Last but not least, we always need help with bugs. Some are technically less challenging than others. Sometimes the best way you can help is to attempt to reproduce the bug and confirm whether or not it’s still an issue. - -**The sweet spot is a “v1 Priority” Issue that’s either a “Good First Issue” or “Help Wanted”.** Yes, please! - -### Create a New Issue -Anyone can create a new Issue. If you’re not sure that your feature or idea is something to work on, start the discussion with an Issue. Describe the idea and problem + solution as clearly as possible, including examples or pseudo code if applicable. It’s also very helpful to `@` mention a maintainer or Core Team member that shares the area of interest. - -Just know that there’s a lot of Issues that shuffle every day. If no one replies, it’s just because people are busy. Reach out in the Forums, Chat, or comment in the Issue. We intend to reply to every Issue that’s opened. If yours doesn’t have a reply, then give us a nudge! - -Lastly, it can often be helpful to start with brief discussion in the community Chat or Forums. Sometimes that’s the quickest way to get feedback and a sense of priority before opening an Issue. - -## Contributing Code - -Redwood's composed of many packages that are designed to work together. Some of these packages are designed to be used outside Redwood too! - -Before you start contributing, you'll want to set up your local development environment. The Redwood repo's top-level [contributing guide](https://github.com/redwoodjs/redwood/blob/main/CONTRIBUTING.md#local-development) walks you through this. Make sure to give it an initial read. - -For details on contributing to a specific package, see the package's README (links provided in the table below). Each README has a section named Roadmap. If you want to get involved but don't quite know how, the Roadmap's a good place to start. See anything that interests you? Go for it! And be sure to let us know—you don't have to have a finished product before opening an issue or pull request. In fact, we're big fans of [Readme Driven Development](https://tom.preston-werner.com/2010/08/23/readme-driven-development.html). - -What you want to do not on the roadmap? Well, still go for it! We love spikes and proof-of-concepts. And if you have a question, just ask! - -### RedwoodJS Framework Packages -|Package|Description| -|:-|:-| -|[`@redwoodjs/api-server`](https://github.com/redwoodjs/redwood/blob/main/packages/api-server/README.md)|Run a Redwood app using Express server (alternative to serverless API)| -|[`@redwoodjs/api`](https://github.com/redwoodjs/redwood/blob/main/packages/api/README.md)|Infrastruction components for your applications UI including logging, webhooks, authentication decoders and parsers, as well as tools to test custom serverless functions and webhooks| -|[`@redwoodjs/auth`](https://github.com/redwoodjs/redwood/blob/main/packages/auth/README.md#contributing)|A lightweight wrapper around popular SPA authentication libraries| -|[`@redwoodjs/cli`](https://github.com/redwoodjs/redwood/blob/main/packages/cli/README.md)|All the commands for Redwood's built-in CLI| -|[`@redwoodjs/core`](https://github.com/redwoodjs/redwood/blob/main/packages/core/README.md)|Defines babel plugins and config files| -|[`@redwoodjs/create-redwood-app`](https://github.com/redwoodjs/redwood/blob/main/packages/create-redwood-app/README.md)|Enables `yarn create redwood-app`—downloads the latest release of Redwood and extracts it into the supplied directory| -|[`@redwoodjs/dev-server`](https://github.com/redwoodjs/redwood/blob/main/packages/dev-server/README.md)|Configuration for the local development server| -|[`@redwoodjs/eslint-config`](https://github.com/redwoodjs/redwood/blob/main/packages/eslint-config/README.md)|Defines Redwood's eslint config| -|[`@redwoodjs/eslint-plugin-redwood`](https://github.com/redwoodjs/redwood/blob/main/packages/eslint-plugin-redwood/README.md)|Defines eslint plugins; currently just prohibits the use of non-existent pages in `Routes.js`| -|[`@redwoodjs/forms`](https://github.com/redwoodjs/redwood/blob/main/packages/forms/README.md)|Provides Form helpers| -|[`@redwoodjs/graphql-server`](https://github.com/redwoodjs/redwood/blob/main/packages/graphql-server/README.md)|Exposes functions to build the GraphQL API, provides services with `context`, and a set of envelop plugins to supercharge your GraphQL API with logging, authentication, error handling, directives and more| -|[`@redwoodjs/internal`](https://github.com/redwoodjs/redwood/blob/main/packages/internal/README.md)|Provides tooling to parse Redwood configs and get a project's paths| -|[`@redwoodjs/router`](https://github.com/redwoodjs/redwood/blob/main/packages/router/README.md)|The built-in router for Redwood| -|[`@redwoodjs/structure`](https://github.com/redwoodjs/redwood/blob/main/packages/structure/README.md)|Provides a way to build, validate and inspect an object graph that represents a complete Redwood project| -|[`@redwoodjs/testing`](https://github.com/redwoodjs/redwood/blob/main/packages/testing/README.md)|Provides helpful defaults when testing a Redwood project's web side| -|[`@redwoodjs/web`](https://github.com/redwoodjs/redwood/blob/main/packages/web/README.md)|Configures a Redwood's app web side: wraps the Apollo Client in `RedwoodApolloProvider`; defines the Cell HOC| - -## Contributing Docs - -First off, thank you for your interest in contributing docs! Redwood prides itself on good developer experience, and that includes good documentation. - -Before you get started, there's an implicit doc-distinction that we should make explicit: all the docs on redwoodjs.com are for helping people develop apps using Redwood, while all the docs on the Redwood repo are for helping people contribute to Redwood. - -Although Developing and Contributing docs are in different places, they most definitely should be linked and referenced as needed. For example, it's appropriate to have a "Contributing" doc on redwoodjs.com that's context-appropriate, but it should link to the Framework's [CONTRIBUTING.md](https://github.com/redwoodjs/redwood/blob/main/CONTRIBUTING.md) (the way this doc does). - -### How Redwood Thinks about Docs - -Before we get into the how-to, a little explanation. When thinking about docs, we find [divio's documentation system](https://documentation.divio.com/) really useful. It's not necessary that a doc always have all four of the dimensions listed, but if you find yourself stuck, you can ask yourself questions like "Should I be explaining? Am I explaining too much? Too little?" to reorient yourself while writing. - -### Docs for Developing Redwood Apps - -redwoodjs.com has three kinds of Developing docs: References, How To's, and The Tutorial. -You can find References and How To's within their respective directories on the redwood/redwood repo: [docs/](https://github.com/redwoodjs/redwood/tree/main/docs) and [how-to/](https://github.com/redwoodjs/redwood/tree/main/docs/how-to). - -The Tutorial is a standalone document that serves a specific purpose as an introduction to Redwood, an aspirational roadmap, and an example of developer experience. As such, it's distinct from the categories mentioned, although it's most similar to How To's. - -#### References - -References are explanation-driven how-to content. They're more direct and to-the-point than The Tutorial and How To's. The idea is much more about finding something or getting something done than any kind of learning journey. - -Before you take on a doc, you should read [Forms](forms.md) and [Router](router.md); they have the kind of content you should be striving for. They're comprehensive yet conversational. - -In general, don't be afraid to go into too much detail. We'd rather you err on the side of too much than too little. One tip for finding good content is searching the forum and repo for "prior art"—what are people talking about where this comes up? - -#### How To's - -How To's are tutorial-style content focused on a specific problem-solution. They usually have a beginner in mind (if not, they should indicate that they don't—put 'Advanced' or 'Deep-Dive', etc., in the title or introduction). How To's may include some explanatory text as asides, but they shouldn't be the majority of the content. - -#### Making a Doc Findable - -If you write it, will they read it? We think they will—if they can find it. - -After you've finished writing, step back for a moment and consider the word(s) or phrase(s) people will use to find what you just wrote. For example, let's say you were writing a doc about configuring a Redwood app. If you didn't know much about configuring a Redwood app, a heading (in the nav bar to the left) like "redwood.toml" wouldn't make much sense, even though it _is_ the main configuration file. You'd probably look for "Redwood Config" or "Settings", or type "how to change Redwood App settings" in the "Search the docs" bar up top, or in Google. - -That is to say, the most useful headings aren't always the most literal ones. Indexing is more than just underlining the "important" words in a text—it's identifying and locating the concepts and topics that are the most relevant to our readers, the users of our documentation. - -So, after you've finished writing, reread what you wrote with the intention of making a list of two to three keywords or phrases. Then, try to use each of those in three places, in this order of priority: - -- the left-nav menu title -- the page title or the first right-nav ("On this page") section title -- the introductory paragraph - -### Docs for Contributing to the Redwood Repo - -These docs are in the Framework repo, redwoodjs/redwood, and explain how to contribute to Redwood packages. They're the docs linked to in the table above. - -In general, they should consist of more straightforward explanations, are allowed to be technically heavy, and should be written for a more experienced audience. But as a best practice for collaborative projects, they should still provide a Vision + Roadmap and identify the project-point person(s) (or lead(s)). - -## What makes for a good Pull Request? -In general, we don’t have a formal structure for PRs. Our goal is to make it as efficient as possible for anyone to open a PR. But there are some good practices, which are flexible. Just keep in mind that after opening a PR there’s more to do before getting to the finish line: -1. Reviews from other contributors and maintainers -2. Update code and, after maintainer approval, merge-in changes to the `main` branch -3. Once PR is merged, it will be released and added to the next version Release Notes with a link for anyone to look at the PR and understand it. - -Some tips and advice: -- **Connect the dots and leave a breadcrumb**: link to related Issues, Forum discussions, etc. Help others follow the trail leading up to this PR. -- **A Helpful Description**: What does the code in the PR do and what problem does it solve? How can someone use the code? Code sample, Screenshot, Quick Video… Any or all of this is so so good. -- **Draft or Work in Progress**: You don’t have to finish the code to open a PR. Once you have a start, open it up! Most often the best way to move an Issue forward is to see the code in action. Also, often this helps identify ways forward before you spend a lot of time polishing. -- **Questions, Items for Discussion, Etc.**: Another reason to open a Draft PR is to ask questions and get direction via review. -- **Loop in a Maintainer for Feedback and Review**: ping someone with an `@`. And nudge again in a few days if there’s no reply. We appreciate it and truly don’t want the PR to get lost in the shuffle! -- **Next Steps**: Once the PR is merged, will there be a follow up step? If so, link to an Issue. How about Docs to-do or Docs to-merge? - -The best thing you can do is look through existing PRs, which will give you a feel for how things work and what you think is helpful. - -### Example PR -If you’re looking for an example of “what makes a good PR”, look no further than this one by Kim-Adeline: -- [Convert component generator to TS #632](https://github.com/redwoodjs/redwood/pull/632) - -Not every PR needs this much information. But it’s definitely helpful when it does! diff --git a/docs/versioned_docs/version-1.0/contributing-walkthrough.md b/docs/versioned_docs/version-1.0/contributing-walkthrough.md deleted file mode 100644 index e0e186ea0768..000000000000 --- a/docs/versioned_docs/version-1.0/contributing-walkthrough.md +++ /dev/null @@ -1,234 +0,0 @@ -# Contributing: Step-by-Step Walkthrough (with Video) - -> ⚡️ **Quick Links** -> -> There are several contributing docs and references, each covering specific topics: -> -> 1. 🧭 [Overview and Orientation](contributing-overview.md) -> 2. 📓 [Reference: Contributing to the Framework Packages](https://github.com/redwoodjs/redwood/blob/main/CONTRIBUTING.md) -> 3. 🪜 **Step-by-step Walkthrough** (👈 you are here) -> 4. 📈 [Current Project Status: v1 Release Board](https://github.com/orgs/redwoodjs/projects/6) -> 5. 🤔 What should I work on? -> - ["Help Wanted" v1 Triage Board](https://redwoodjs.com/good-first-issue) -> - [Discovery Process and Open Issues](contributing-overview.md#what-should-i-work-on) - - -## Video Recording of Complete Contributing Process -The following recording is from a Contributing Workshop, following through the exact steps outlined below. The Workshop includes additional topics along with Q&A discussion. - - - -## Prologue: Getting Started with Redwood and GitHub (and git) -These are the foundations for contributing, which you should be familiar with before starting the walkthrough. - -[**The Redwood Tutorial**](tutorial/foreword.md) - -The best (and most fun) way to learn Redwood and the underlying tools and technologies. - -**Docs and How To** - -- Start with the [Introduction](https://github.com/redwoodjs/redwood/blob/main/README.md) Doc -- And browse through [How To's](how-to/index) - -### GitHub (and Git) -Diving into Git and the GitHub workflow can feel intimidating if you haven’t experienced it before. The good news is there’s a lot of great material to help you learn and be committing in no time! - -- [Introduction to GitHub](https://lab.github.com/githubtraining/introduction-to-github) (overview of concepts and workflow) -- [First Day on GitHub](https://lab.github.com/githubtraining/first-day-on-github) (including Git) -- [First Week on GitHub](https://lab.github.com/githubtraining/first-week-on-github) (parts 3 and 4 might be helpful) - -## The Full Workflow: From Local Development to a New PR - -### Definitions -#### Redwood “Project” -We refer to the codebase of a Redwood application as a Project. This is what you install when you run `yarn create redwood-app `. It’s the thing you are building with Redwood. - -Lastly, you’ll find the template used to create a new project (when you run create redwood-app) here in GitHub: [redwoodjs/redwood/packages/create-redwood-app/template/](https://github.com/redwoodjs/redwood/tree/main/packages/create-redwood-app/template) - -We refer to this as the **CRWA Template or Project Template**. - -#### Redwood “Framework” -The Framework is the codebase containing all the packages (and other code) that is published on NPMjs.com as `@redwoodjs/`. The Framework repository on GitHub is here: [https://github.com/redwoodjs/redwood](https://github.com/redwoodjs/redwood) - -### Development tools -These are the tools used and recommended by the Core Team. - -**VS Code** -[Download VS Code](https://code.visualstudio.com/download) -This has quickly become the de facto editor for JavaScript and TypeScript. Additionally, we have added recommended VS Code Extensions to use when developing both the Framework and a Project. You’ll see a pop-up window asking you about installing the extensions when you open up the code. - -**GitHub Desktop** -[Download GitHub Desktop](https://desktop.github.com) -You’ll need to be comfortable using Git at the command line. But the thing ew like best about GitHub Desktop is how easy it makes workflow across GitHub -- GitHub Desktop -- VS Code. You don’t have to worry about syncing permissions or finding things. You can start from a repo on GitHub.com and use Desktop to do everything from “clone and open on your computer” to returning back to the site to “open a PR on GitHub”. - -**[Mac OS] iTerm and Oh-My-Zsh** -There’s nothing wrong with Terminal (on Mac) and bash. (If you’re on Windows, we highly recommend using Git for Windows and Git bash.) But we enjoy using iTerm ([download](https://iterm2.com)) and Zsh much more (use [Oh My Zsh](https://ohmyz.sh)). Heads up, you can get lost in the world of theming and adding plugins. We recommend keeping it simple for awhile before taking the customization deep dive -😉 - -**[Windows] Git for Windows with Git Bash or WSL(2)** -Unfortunately, there are a lot of “gotchas” when it comes to working with Javascript-based frameworks on Windows. We do our best to point out (and resolve) issues, but our priority focus is on developing a Redwood app vs contributing to the Framework. (If you’re interested, there’s a lengthy Forum conversation about this with many suggestions.) - -All that said, we highly recommend using one of the following setups to maximize your workflow: -1. Use [Git for Windows and Git Bash](how-to/windows-development-setup.md) (included in installation) -2. Use [WSL following this setup guide on the Forums](https://community.redwoodjs.com/t/windows-subsystem-for-linux-setup/2439) - -Lastly, the new GitPod integration is a great option and only getting better. You might just want to start using it from the beginning (see section below in “Local Development Setup”). - -**GitPod** -We recently added an integration with [GitPod](http://gitpod.io) that automatically creates a Framework dev workspace, complete with test project, in a browser-based VS Code environment. It’s pretty amazing and we highly recommend giving it a shot. (If you’re developing on Windows, it’s also an amazing option for you anytime you run into something that isn’t working correctly or supported.) - -But don’t skip out reading the following steps in “Local Development Setup” — GitPod uses the same workflow and tools to initialize. If you want to develop in GitPod, you’ll need to understand how it all works. - -But when you’re ready, learn how to use it in the section at the end [“GitPod: Browser-based Development”](#gitpod-browser-based-development). - -### Local Development Setup -#### Step 1: Redwood Framework -1. **Fork the [Redwood Framework](https://github.com/redwoodjs/redwood)** into a personal repo -2. Using GitHub Desktop, **open the Framework Codebase** in a VS Code workspace -3. Commands to “**start fresh**” when working on the Framework - - `yarn install`: This installs the package dependencies in /node_modules using Yarn package manager. This command is the same as just typing `yarn`. Also, if you ever switch branches and want to make sure the install dependencies are correct, you can run `yarn install --force` (shorthand `yarn -f`). - - `git clean -fxd`: *You’ll only need to do this if you’ve already been developing and want to “start over” and reset your codebase*. This command will permanently delete everything that is .gitignored, e.g. /node_modules and /dist directories with package builds. When switching between branches, this command makes sure nothing is carried over that you don’t want. (Warning: it will delete .env files in a Redwood Project. To avoid this, you can use `git clean -fxd -e .env`.) -4. **Create a new branch** from the `main` branch -First make sure you’ve pulled all changes from the remote origin (GitHub repo) into your local branch. (If you just cloned from your fork, you should be up to date.) Then create a new branch. The nomenclature used by David Price is `-description-with-hyphens`, e.g. `dsp-add-eslint-config-redwood-toml`. It's simple to use VS Code or GitHub Desktop to manage branches. You can also do this via the CLI git checkout command. - -#### Step 2: Test Project -There are several options for creating a local Redwood Project to use during development. Anytime you are developing against a test project, there are some specific gotchas to keep in mind: -- New projects always use the latest stable version of the Redwood packages, which will not be up to date with the latest Framework code in the `main` branch. -- To use the packages corresponding with the latest code in the Framework `main` branch, you can use the canary version published to NPM. All you need to do to install the canary versions is run `yarn rw upgrade --tag canary` in your Project -- Using a cloned project or repo? Just know there are likely breaking changes in `main` that haven’t been applied. You can examine merged PRs with the “breaking” label for more info. -- Just because you are using canary doesn’t mean you are using your local Framework branch code! Make sure you run `yarn rwfw project:sync`. And anytime you switch branches or get out of sync, you might need to start over beginning with the `git clean -fxd` command - -With those details out of the way, now is the time to choose an option below that meets your needs based on functionality and codebase version. - -**Build a Functional Test Project [Recommended]** -1. 👉 **Use the build script to create a test project**: From the Framework root directory, run `yarn build:test-project `. This command installs a new project using the Template codebase from your current Framework branch, it then adds Tutorial features, and finally it initializes the DB (with seed data!). It should work 90% of the time and is the recommended starting place. We also use this out-of-the-box with GitPod. - -**Other Options to create a project** - -2. **Install a fresh project using the local Framework template code:** Sometimes you need to create a project that uses the Template codebase in your local branch of the Framework, e.g. your changes include modifications to the CRWA Template and need to be tested. Running the command above is exactly the same as `yarn create redwood- app …`, only it runs the command from your local Framework package using the local Template codebase. Note: this is the same command used at the start of the `yarn build:test-project` command. -``` -yarn babel-node packages/create-redwood-app/src/create-redwood-app.js -``` - -3. **Clone the Redwood Tutorial App repo:** This is the codebase to use when starting the Redwood Tutorial Part 2. It is updated to the latest version and has the Blog features. This is often something we use for local development. Note: be sure to upgrade to canary and look out for breaking changes coming with the next release. - - -4. **Install a fresh project**: `yarn create redwood-app ` If you just need a fresh installation 1) using the latest version template codebase and 2) without any features, then just install a new Redwood project. Note: this can have the same issues regarding the need to upgrade to canary and addressing breaking changes (see Notes from items 2 and 3 above). - -> Note: All the options above currently set the language to JavaScript. If you would like to work with TypeScript, you can add the option `--typescript` to either of the commands that run the create-redwood-app installation. - -#### Step 3: Link the local Framework with the local test Project -Once you work on the Framework code, you’ll most often want to run the code in a Redwood app for testing. However, the Redwood Project you created for testing is currently using the latest version (or canary) packages of Redwood published on NPMjs.com, e.g. [@redwoodjs/core](https://www.npmjs.com/package/@redwoodjs/core) - -So we’ll use the Redwood Framework (rwfw) command to connect our local Framework and test Projects, which allows the Project to run on the code for Packages we are currently developing. - -Run this command from the CLI in your test Project: -``` -RWFW_PATH= yarn rwfw project:sync -``` - -For Example: -``` -cd redwood-project -RWFW_PATH=~/redwood yarn rwfw project:sync -``` - -RWFW_PATH is the path to your local copy of the Redwood Framework. _Once provided to rwfw, it'll remember it and you shouldn't have to provide it again unless you move it._ - -> **Heads up for Windows Devs** -> Depending on your dev setup, Windows might balk at you setting the env var RWFW_PATH at the beginning of the command like this. If so, try prepending with `cross-env`, e.g. `yarn cross-env RWFW_PATH=~/redwood yarn rwfw` ... Or you can add the env var and value directly to your shell before running the command. - -As project:sync starts up, it'll start logging to the console. In order, it: -1. cleans and builds the framework -2. copies the framework's dependencies to your project -3. runs yarn install in your project -4. copies over the framework's packages to your project -5. waits for changes - -Step two is the only explicit change you'll see to your project. You'll see that a ton of packages have been added to your project's root package.json. - -All done? You’re ready to kill the link process with “ctrl + c”. You’ll need to confirm your root package.json no longer has the added dependencies. And, if you want to reset your test-project, you should run `yarn install --force`. - -#### Step 4: Framework Package(s) Local Testing -Within your Framework directory, use the following tools and commands to test your code: -1. **Build the packages**: `yarn build` - - to delete all previous build directories: yarn build:clean -2. **Syntax and Formatting**: `yarn lint` - - to fix errors or warnings: `yarn lint:fix` -3. **Run unit tests for each package**: `yarn test` -4. **Run through the Cypress E2E integration tests**: `yarn e2e` -5. **Check Yarn resolutions and package.json format**: `yarn check` - -All of these checks are included in Redwood’s GitHub PR Continuous Integration (CI) automation. However, it’s good practice to understand what they do by using them locally. The E2E tests aren’t something we use every time anymore (because it takes a while), but you should learn how to use it because it comes in handy when your code is failing tests on GitHub and you need to diagnose. - -> **Heads up for Windows Devs** -> The Cypress E2E does *not* work on Windows. Two options are available if needed: -> 1. Use GitPod (see related section for info) -> 2. When you create a PR, just ask for help from a maintainer - -#### Step 5: Open a PR 🚀 -You’ve made it to the fun part! It’s time to use the code you’re working on to create a new PR into the Redwood Framework `main` branch. - -We use GitHub Desktop to walk through the process of: -- committing my changes to my development branch -- Publishing (pushing) my branch and changes to my GitHub repo fork of the Redwood Framework -- Opening a PR requesting to merge my forked-repo branch into the Redwood Framework `main` branch - -Refer to the section above “What makes for a good Pull Request?” for advice on opening your PR. - -**Note:** Make sure you check the box to “allow project maintainers to update the code” (I’m not sure about the specific description used). This helps a PR move forward more quickly as branches always need to be updated from `main` before we can merge. - -**When is my code “ready” to open a PR?** -Most of the action, communication, and decisions happen within a PR. A common mistake new contributors make is *waiting* until their code is “perfect” before opening a PR. Assuming your PR has some code changes, it’s great practice to open a Draft PR (setting during the PR creation), which you can use to start discussion and ask questions. PRs are closed all the time without being merged, often because they are replaced by another PR resulting from decisions and discussion. It’s part of the process. More importantly, it means collaboration is happening! - -What isn’t a fun experience is spending a whole bunch of time on code that ends up not being the correct direction or is unnecessary/redundant to something that already exists. This is a part of the learning process. But it’s another reason to open a draft PR sooner than later to get confirmation and questions out of the way before investing time into refining and details. - -When in doubt, just try first and ask for help and direction! - -### GitPod: Browser-based Development -[GitPod](http://gitpod.io) has recently been integrated with Redwood to JustWork™ with any branch or PR. When a virtual GitPod workspace is initialized, it automatically: -1. Checks-out the code from your branch or PR -2. Run Yarn installation -3. Creates the functional Test Project via `yarn build:test-project` -4. Syncs the Framework code with the Test Project -5. Starts the Test Project dev server -6. 🤯 - -> **Chrome works best** -> We’ve noticed some bugs using GitPod with either Brave or Safari. Currently we recommend sticking to Chrome (although it’s worth trying out Edge and Firefox). - -**Demo of GitPod** -David briefly walks-through an automatically prebuilt GitPod workspace here: -- [GitPod + RedwoodJS 3-minute Walkthrough](https://youtu.be/_kMuTW3x--s) - -Make sure you watch until the end where David shows how to set up your integration with GitHub and VS Code sync. 🤩 - -**Start a GitPod Workspace** -There are two ways to get started with GitPod + Redwood. - -*Option 1: Open a PR* -Every PR will trigger a GitPod prebuild using the PR branch. Just look for GitPod in the list of checks at the bottom of the PR — click the “Details” link and away you’ll go! - -PR Checks - -*Option 2: Use the link from your project or branch* - -You can initialize a workspace using this URL pattern: - -``` -https://gitpod.io/# -``` - -For example, this link will start a workspace using the RedwoodJS main branch: -- https://gitpod.io/#https://github.com/redwoodjs/redwood - -And this link will start a workspace for a PR #3434: -- https://gitpod.io/#https://github.com/redwoodjs/redwood/pull/3434 - - diff --git a/docs/versioned_docs/version-1.0/cors.md b/docs/versioned_docs/version-1.0/cors.md deleted file mode 100644 index 494915a2cb95..000000000000 --- a/docs/versioned_docs/version-1.0/cors.md +++ /dev/null @@ -1,266 +0,0 @@ -# CORS - -CORS stands for [Cross Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). In a nutshell, by default, browsers aren't allowed to access resources outside their own domain. - -## When you need to worry about CORS - -If your api and web sides are deployed to different domains, you'll have to worry about CORS. For example, if your web side is deployed to `example.com` but your api is `api.example.com`. For security reasons your browser will not allow XHR requests (like the kind that the GraphQL client makes) to a domain other than the one currently in the browser's address bar. - -This will become obvious when you point your browser to your site and see none of your GraphQL data. When you look in the web inspector you'll see a message along the lines of: - -> ⛔️ Access to fetch https://api.example.com has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. - -## Avoiding CORS - -Dealing with CORS can complicate your app and make it harder to deploy to new hosts, run in different environments, etc. Is there a way to avoid CORS altogether? - -Yes! If you can add a proxy between your web and api sides, all requests will *appear* to be going to and from the same domain (the web side, even though behind the scenes they are forwarded somewhere else). This functionality is included automatically with hosts like [Netlify](https://docs.netlify.com/routing/redirects/rewrites-proxies/#proxy-to-another-service) or [Vercel](https://vercel.com/docs/cli#project-configuration/rewrites). With a host like [Render](https://render-web.onrender.com/docs/deploy-redwood#deployment) you can enable a proxy with a simple config option. Most providers should provide this functionality through a combination of provider-specific config and/or web server configuration. - -## GraphQL Config - -You'll need to add CORS headers to GraphQL responses. You can do this easily enough by adding the `cors` option in `api/src/functions/graphql.js` (or `graphql.ts`): - -```diff -export const handler = createGraphQLHandler({ - loggerConfig: { logger, options: {} }, - directives, - sdls, - services, -+ cors: { -+ origin: 'https://www.example.com', // <-- web side domain -+ }, - onException: () => { - db.$disconnect() - }, -}) -``` - -Note that the `origin` needs to be a complete URL including the scheme (`https`). This is the domain that requests are allowed to come *from*. In this example we assume the web side is served from `https://www.example.com`. If you have multiple servers that should be allowed to access the api, you can pass an array of them instead: - -```javascript -cors: { - origin: ['https://example.com', 'https://www.example.com'] -}, -``` - -The proper one will be included in the CORS header depending on where the response came from. - -## Authentication Config - -The following config only applies if you're using [dbAuth](authentication.md#self-hosted-auth-installation-and-setup), which is Redwood's own cookie-based auth system. - -You'll need to configure several things: - -* Add CORS config for GraphQL -* Add CORS config for the auth function -* Cookie config for the auth function -* Allow sending of credentials in GraphQL XHR requests -* Allow sending of credentials in auth function requests - -Here's how you configure each of these: - -### GraphQL CORS Config - -You'll need to add CORS headers to GraphQL responses, and let the browser know to send up cookies with any requests. Add the `cors` option in `api/src/functions/graphql.js` (or `graphql.ts`) with an additional `credentials` property: - -```diff -export const handler = createGraphQLHandler({ - loggerConfig: { logger, options: {} }, - directives, - sdls, - services, -+ cors: { -+ origin: 'https://www.example.com', // <-- web side domain -+ credentials: true, -+ }, - onException: () => { - db.$disconnect() - }, -}) -``` - -`origin` is the domain(s) that requests come *from* (the web side). - -### Auth CORS Config - -Similar to the `cors` options being sent to GraphQL, you can set similar options in `api/src/functions/auth.js` (or `auth.ts`): - -```diff -const authHandler = new DbAuthHandler(event, context, { - db: db, - authModelAccessor: 'user', - authFields: { - id: 'id', - username: 'email', - hashedPassword: 'hashedPassword', - salt: 'salt', - resetToken: 'resetToken', - resetTokenExpiresAt: 'resetTokenExpiresAt', - }, -+ cors: { -+ origin: 'https://www.example.com', // <-- web side domain -+ credentials: true, -+ }, - cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: true, - }, - forgotPassword: forgotPasswordOptions, - login: loginOptions, - resetPassword: resetPasswordOptions, - signup: signupOptions, -}) -``` - -Just like the GraphQL config, `origin` is the domain(s) that requests come *from* (the web side). - -### Cookie Config - -In order to be able accept cookies from another domain we'll need to make a change to the `SameSite` option in `api/src/functions/auth.js` and set it to `None`: - -```javascript {4} - cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'None', - Secure: true, - }, -``` - -### GraphQL XHR Credentials - -Next we need to tell the GraphQL client to include credentials (the dbAuth cookie) in any requests. This config goes in `web/src/App.js`: - -```javascript {5-9} -const App = () => ( - - - - - - - - - -) -``` - -### Auth XHR Credentials - -Finally, we need to tell dbAuth to include credentials in its own XHR requests: - -```javascript {4-7} -const App = () => ( - - - - - - - - - -) -``` - -## Testing CORS Locally - -If you've made the configuration changes above, `localhost` testing should continue working as normal. But, if you want to make sure your CORS config works without deploying to the internet somewhere, you'll need to do some extra work. - -### Serving Sides to the Internet - -First, you need to get the web and api sides to be serving from different hosts. A tool like [ngrok](https://ngrok.com/) or [localhost.run](https://localhost.run/) allows you to serve your local development environment over a real domain to the rest of the internet (on both `http` and `https`). - -You'll need to start two tunnels, one for the web side (this example assumes ngrok): - -```bash -> ngrok http 8910 - -Session Status online -Account Your Name (Plan: Pro) -Version 2.3.40 -Region United States (us) -Web Interface http://127.0.0.1:4040 -Forwarding http://3c9913de0c00.ngrok.io -> http://localhost:8910 -Forwarding https://3c9913de0c00.ngrok.io -> http://localhost:8910 -``` - -And another for the api side: - -```bash -> ngrok http 8911 - -Session Status online -Account Your Name (Plan: Pro) -Version 2.3.40 -Region United States (us) -Web Interface http://127.0.0.1:4040 -Forwarding http://fb6d701c44b5.ngrok.io -> http://localhost:8911 -Forwarding https://fb6d701c44b5.ngrok.io -> http://localhost:8911 -``` - -Note the two different domains. Copy the `https` domain from the api side because we'll need it in a moment. Even if the Redwood dev server isn't running you can leave these tunnels running, and when the dev server *does* start, they'll just start on those domains again. - -### `redwood.toml` Config - -You'll need to make two changes here: - -1. Bind the server to all network interfaces -2. Point the web side to the api's domain - -Normally the dev server only binds to `127.0.0.1` (home sweet home) which means you can only access it from your local machine using `localhost` or `127.0.0.1`. To tell it to bind to all network interfaces, and to be available to the outside world, add this `host` option: - -```toml {4} -[web] - title = "Redwood App" - port = 8910 - host = '0.0.0.0' - apiUrl = '/.redwood/functions' - includeEnvironmentVariables = [] -[api] - port = 8911 -[browser] - open = true -``` - -We'll also need to tell the web side where the api side lives. Update the `apiUrl` to whatever domain your api side is running on (remember the domain you copied from from ngrok): - -```toml {5} -[web] - title = "Redwood App" - port = 8910 - host = '0.0.0.0' - apiUrl = 'https://fb6d701c44b5.ngrok.io' - includeEnvironmentVariables = [] -[api] - port = 8911 -[browser] - open = true -``` - -Where you get this domain from will depend on how you expose your app to the outside world (this example assumes ngrok). - -### Starting the Dev Server - -You'll need to apply an option when starting the dev server to tell it to accept requests from any host, not just `localhost`: - -```bash -> yarn rw dev --fwd="--allowed-hosts all" -``` - -### Wrapping Up - -Now you should be able to open the web side's domain in a browser and use your site as usual. Test that GraphQL requests work, as well as authentication if you are using dbAuth. diff --git a/docs/versioned_docs/version-1.0/custom-web-index.md b/docs/versioned_docs/version-1.0/custom-web-index.md deleted file mode 100644 index 111958e8140d..000000000000 --- a/docs/versioned_docs/version-1.0/custom-web-index.md +++ /dev/null @@ -1,36 +0,0 @@ -# Custom Web Index - -You might've noticed that there's no call to `ReactDOM.render` anywhere in your Redwood App (`v0.26` and greater). That's because Redwood automatically mounts your `` in `web/src/App.js` to the DOM. But if you need to customize how this happens, you can provide a file called `index.js` in `web/src` and Redwood will use that instead. - -## Setup - -To make this easy to do, there's a setup command that'll give you the file you need where you need it: - -``` -yarn rw setup custom-web-index -``` - -This generates a file named `index.js` in `web/src` that looks like this: - -```js -// web/src/index.js - -import ReactDOM from 'react-dom' - -import App from './App' -/** - * When `#redwood-app` isn't empty then it's very likely that you're using - * prerendering. So React attaches event listeners to the existing markup - * rather than replacing it. - * https://reactjs.org/docs/react-dom.html#hydrate - */ -const rootElement = document.getElementById('redwood-app') - -if (rootElement.hasChildNodes()) { - ReactDOM.hydrate(, rootElement) -} else { - ReactDOM.render(, rootElement) -``` - - -This is actually the same file Redwood uses [internally](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/entry/index.js). So even if you don't customize anything any further than this, things will still work the way the should! diff --git a/docs/versioned_docs/version-1.0/data-migrations.md b/docs/versioned_docs/version-1.0/data-migrations.md deleted file mode 100644 index accfd1e8eae1..000000000000 --- a/docs/versioned_docs/version-1.0/data-migrations.md +++ /dev/null @@ -1,160 +0,0 @@ -# Data Migrations - -> Data Migrations are available as of RedwoodJS v0.15 - -There are two kinds of changes you can make to your database: - -* Changes to structure -* Changes to content - -In Redwood, [Prisma Migrate](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-migrate) takes care of codifying changes to your database *structure* in code by creating a snapshot of changes to your database that can be reliably repeated to end up in some known state. - -To track changes to your database *content*, Redwood includes a feature we call **Data Migration**. As your app evolves and you move data around, you need a way to consistently declare how that data should move. - -Imagine a `User` model that contains several columns for user preferences. Over time, you may end up with more and more preferences to the point that you have more preference-related columns in the table than you do data unique to the user! This is a common occurrence as applications grow. You decide that the app should have a new model, `Preference`, to keep track of them all (and `Preference` will have a foreign key `userId` to reference it back to its `User`). You'll use Prisma Migrate to create the new `Preference` model, but how do you copy the preference data to the new table? Data migrations to the rescue! - -## Installing - -Just like Prisma, we will store which data migrations have run in the database itself. We'll create a new database table `DataMigration` to keep track of which ones have run already. - -Rather than create this model by hand, Redwood includes a CLI tool to add the model to `schema.prisma` and create the DB migration that adds the table to the database: - - yarn rw data-migrate install - -You'll see a new directory created at `api/db/dataMigrations` which will store our individual migration tasks. - -Take a look at `schema.prisma` to see the new model definition: - -```javascript -// api/db/schema.prisma - -model RW_DataMigration { - version String @id - name String - startedAt DateTime - finishedAt DateTime -} -``` - -The install script also ran `yarn rw prisma migrate dev --create-only` automatically so you have a DB migration ready to go. You just need to run the `prisma migrate dev` command to apply it: - - yarn rw prisma migrate dev - -## Creating a New Data Migration - -Data migrations are just plain Typescript or Javascript files which export a single anonymous function that is given a single argument—an instance of `PrismaClient` called `db` that you can use to access your database. The files have a simple naming convention: - - {version}-{name}.js - -Where `version` is a timestamp, like `20200721123456` (an ISO8601 datetime without any special characters or zone identifier), and `name` is a param-case human readable name for the migration, like `copy-preferences`. - -To create a data migration we have a generator: - - yarn rw generate dataMigration copyPreferences - -This will create `api/db/dataMigrations/20200721123456-copy-preferences.js`: - -```javascript -// api/db/dataMigrations/20200721123456-copy-preferences.js - -export default async ({ db }) => { - // Migration here... -} -``` - -> **Why such a long name?** -> -> So that if multiple developers are creating data migrations, the chances of them creating one with the exact same filename is essentially zero, and they will all run in a predictable order—oldest to newest. - -Now it's up to you to define your data migration. In our user/preference example, it may look something like: - -```javascript -// api/db/dataMigrations/20200721123456-copy-preferences.js - -const asyncForEach = async (array, callback) => { - for (let index = 0; index < array.length; index++) { - await callback(array[index], index, array) - } -} - -export default async ({ db }) => { - const users = await db.user.findMany() - - asyncForEach(users, async (user) => { - await db.preference.create({ - data: { - newsletter: user.newsletter, - frequency: user.frequency, - theme: user.theme, - user: { connect: { id: user.id } } - } - }) - }) -} -``` - -This loops through each existing `User` and creates a new `Preference` record containing each of the preference-related fields from `User`. - -> Note that in a case like this where you're copying data to a new table, you would probably delete the columns from `User` afterwards. This needs to be a two step process! -> -> 1. Create the new table (db migration) and then move the data over (data migration) -> 2. Remove the unneeded columns from `User` -> -> When going to production, you would need to run this as two separate deploys to ensure no data is lost. -> -> The reason is that all DB migrations are run and *then* all data migrations. So if you had two DB migrations (one to create `Preference` and one to drop the unneeded columns from `User`) they would both run before the Data Migration, so the columns containing the preferences are gone before the data migration gets a chance to copy them over! -> -> **Remember**: Any destructive action on the database (removing a table or column especially) needs to be a two step process to avoid data loss. - -## Running a Data Migration - -When you're ready, you can execute your data migration with `data-migrate`'s `up` command: - - yarn rw data-migrate up - -This goes through each file in `api/db/dataMigrations`, compares it against the list of migrations that have already run according to the `DataMigration` table in the database, and executes any that aren't present in that table, sorted oldest to newest based on the timestamp in the filename. - -Any logging statements (like `console.info()`) you include in your data migration script will be output to the console as the script is running. - -If the script encounters an error, the process will abort, skipping any following data migrations. - -> The example data migration above didn't include this for brevity, but you should always run your data migration [inside a transaction](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/transactions#bulk-operations-experimental) so that if any errors occur during execution the database will not be left in an inconsistent state where only *some* of your changes were performed. - -## Long-term Maintainability - -Ideally you can run all database migrations and data migrations from scratch (like when a new developer joins the team) and have them execute correctly. Unfortunately you don't get that ideal scenario by default. - -Take our example above—what happens when a new developer comes long and attempts to setup their database? All DB migrations will run first (including the one that drops the preference-related columns from `User`) before the data migrations run. They will get an error when they try to read something like `user.newsletter` and that column doesn't exist! - -One technique to combat this is to check for the existence of these columns before the data migration does anything. If `user.newsletter` doesn't exist, then don't bother running the data migration at all and assume that your [seed data](cli-commands.md#prisma-db-seed) is already in the correct format: - -```javascript{4,15} -export default async ({ db }) => { - const users = await db.user.findMany() - - if (typeof user.newsletter !== undefined) { - asyncForEach(users, async (user) => { - await db.preference.create({ - data: { - newsletter: user.newsletter, - frequency: user.frequency, - theme: user.theme, - user: { connect: { id: user.id } } - } - }) - }) - } -} -``` - -## Lifecycle Summary - -Run once: - - yarn rw data-migrate install - yarn rw prisma migrate dev - -Run every time you need a new data migration: - - yarn rw generate dataMigration migrationName - yarn rw data-migrate up diff --git a/docs/versioned_docs/version-1.0/deploy.md b/docs/versioned_docs/version-1.0/deploy.md deleted file mode 100644 index 069ce759828c..000000000000 --- a/docs/versioned_docs/version-1.0/deploy.md +++ /dev/null @@ -1,313 +0,0 @@ -# Deploy - -Redwood is designed for both serverless and traditional infrastructure deployments, offering a unique continuous deployment process in both cases: - -1. code is committed to a repository on GitHub, GitLab, or Bitbucket, which triggers the deployment -2. the Redwood API Side and Web Side are individually prepared via a build process -3. during the build process, any database related actions are run (e.g. migrations) -4. the hosting provider deploys the built Web static assets to a CDN and the API code to a serverless backend (e.g. AWS Lambdas) - -Currently, these are the officially supported deploy targets: -- [Flightcontrol](https://www.flightcontrol.dev?ref=redwood) -- [Layer0](https://layer0.co) -- [Netlify](https://www.netlify.com/) -- [Render](https://render.com) -- [Serverless.com](https://serverless.com) -- [Vercel](https://vercel.com) - - -Redwood has a CLI generator that adds the code and configuration required by the specified provider (see the [CLI Doc](cli-commands.md#deploy-config) for more information): -```shell -yarn rw setup deploy -``` - -There are examples of deploying Redwood on other providers such as Google Cloud and direct to AWS. You can find more information by searching the [GitHub Issues](https://github.com/redwoodjs/redwood/issues) and [Forums](https://community.redwoodjs.com). - - -## General Deployment Setup -Deploying Redwood requires setup for the following four categories. - -### 1. Host Specific Configuration -Each hosting provider has different requirements for how (and where) the deployment is configured. Sometimes you'll need to add code to your repository, configure settings in a dashboard, or both. You'll need to read the provider specific documentation. - -The most important Redwood configuration is to set the `apiUrl` in your `redwood.toml` This sets the API path for your serverless functions specific to your hosting provider. - -### 2. Build Command -The build command is used to prepare the Web and API for deployment. Additionally, other actions can be run during build such as database migrations. The Redwood build command must specify one of the supported hosting providers (aka `target`): - -```shell -yarn rw deploy -``` - -For example: - -```shell -# Build command for Netlify deploy target -yarn rw deploy netlify -``` - -```shell -# Build command for Vercel deploy target -yarn rw deploy vercel -``` - -```shell -# Build command for AWS Lambdas using the https://serverless.com framework -yarn rw deploy aws serverless --side api -``` - -```shell -# Build command for Layer0 deploy target -yarn rw deploy layer0 -``` -### 3. Prisma and Database -Redwood uses Prisma for managing database access and migrations. The settings in `api/prisma/schema.prisma` must include the correct deployment database, e.g. postgresql, and the database connection string. - -To use PostgreSQL in production, include this in your `schema.prisma`: - -```javascript -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} -``` - -The `url` setting above accesses the database connection string via an environment variable, `DATABASE_URL`. Using env vars is the recommended method for both ease of development process as well as security best practices. - -Whenever you make changes to your `schema.prisma`, you must run the following command: -```shell -$ yarn rw prisma migrate dev # creates and applies a new Prisma DB migration -``` - -> Note: when setting your production DATABASE_URL env var, be sure to also set any connection-pooling or sslmode parameters. For example, if using Supabase Postgres with pooling, then you would use a connection string similar to `postgresql://postgres:mydb.supabase.co:6432/postgres?sslmode=require&pgbouncer=true` that uses a specific 6432 port, informs Prisma to consider pgBouncer, and also to use SSL. See: [Connection Pooling](connection-pooling.md) for more info. - - - -### 4. Environment Variables -Any environment variables used locally, e.g. in your `env.defaults` or `.env`, must also be added to your hosting provider settings. (See documentation specific to your provider.) - -Additionally, if your application uses env vars on the Web Side, you must configure Redwood's build process to make them available in production. See the [Redwood Environment Variables doc](environment-variables.md) for instructions. - -## Flightcontrol Deploy - - [Flightcontrol](https://www.flightcontrol.dev?ref=redwood) is a new platform that brings world-class deployment DX natively to your AWS account. It's easy to use but lets you pop the hood and leverage the raw power of AWS when needed. It currently supports servers, static sites, and databases which makes it a perfect fit for hosting scalable Redwood apps. - - ### Flightcontrol Deploy Setup - - 1. In your project, run the command `yarn rw setup deploy flightcontrol` - 2. Commit the changes and push to github - 3. If you don't have an account, sign up at [app.flightcontrol.dev/signup](https://app.flightcontrol.dev/signup?ref=redwood) - 4. Create a new project at [app.flightcontrol.dev/projects/new/1](https://app.flightcontrol.dev/projects/new/1) - 1. Connect your Github account and select your repo - 2. Select "Config Type" as `flightcontrol.json` - 3. Select the AWS region to deploy to. - 4. Click "Create Project" - -If you have *any* problems or questions, Flightcontrol is very responsive in [their support Discord](https://discord.gg/yY8rSPrD6q). - -## Layer0 Deploy - -[Layer0](https://layer0.co) extends the capabilities of a traditional CDN by not only hosting your static content, but also providing server-side rendering for progressive web applications as well as caching both your APIs and HTML at the network edge to provide your users with the fastest browsing experience. - -### Layer0 Deploy Setup - -In order to deploy your RedwoodJS project to Layer0, the project must first be initialized with the Layer0 CLI. - -1. In your project, run the command `yarn rw setup deploy layer0`. -2. Verify the changes to your project, commit and push to your repository. -3. Deploy your project to Layer0 - 1. If this is your first time deploying to Layer0, the interactive CLI will prompt to authenticate using your browser. You can start the deploy by running `yarn rw deploy layer0`. - 2. If you are deploying from a **non-interactive** environment, you will need to create an account on [Layer0 Developer Console](https://app.layer0.co) first and setup a [deploy token](https://docs.layer0.co/guides/deploy_apps#section_deploy_from_ci). Once the deploy token is created, save it as a secret to your environment. You can start the deploy by running `yarn rw deploy layer0 --token=XXX`. -4. Follow the link in the output to view your site live once deployment has completed! - -For more information on deploying to Layer0, check out the [documentation](https://docs.layer0.co). - -## Netlify Deploy - -### Netlify tl;dr Deploy -If you simply want to experience the Netlify deployment process without a database and/or adding custom code, you can do the following: -1. create a new redwood project: `yarn create redwood-app ./netlify-deploy` -2. after your "netlify-deploy" project installation is complete, init git, commit, and add it as a new repo to GitHub, BitBucket, or GitLab -3. run the command `yarn rw setup deploy netlify` and commit and push changes -4. use the Netlify [Quick Start](https://app.netlify.com/signup) to deploy - -### Netlify Complete Deploy Walkthrough -For the complete deployment process on Netlify, see the [Tutorial Deployment section](tutorial/chapter4/deployment.md). - -## Render Deploy -Render is a unified cloud to build and run all your apps and websites with free SSL, a global CDN, private networks and auto deploys from Git — **database included**! -### Render tl;dr Deploy -If you simply want to experience the Render deployment process, including a Postgres or SQLite database, you can do the following: -1. create a new redwood project: `yarn create redwood-app ./render-deploy` -2. after your "render-deploy" project installation is complete, init git, commit, and add it as a new repo to GitHub or GitLab -3. run the command `yarn rw setup deploy render`, use the flag `--database` to select from `postgresql`, `sqlite` or `none` to proceed without a database [default : `postgresql`] -4. follow the [Render Redwood Deploy Docs](https://render.com/docs/deploy-redwood) for detailed instructions - -## Serverless Deploy - ->The following instructions assume you have read the [General Deployment Setup](#general-deployment-setup) section above. - -Yes, the name is confusing, but Serverless provides a very interesting option—deploy to your own cloud service account and skip the middleman entirely! By default, Serverless just orchestrates starting up services in your cloud provider of choice and pushing your code up to them. Any bill you receive is from your hosting provider (although many offer a generous free tier). You can optionally use the [Serverless Dashboard](https://www.serverless.com/dashboard/) to monitor your deploys and setup CI/CD to automatically deploy when pushing to your repo of choice. If you don't setup CI/CD you actually deploy from your development machine (or another designated machine you've setup to do the deployment). - -Currently we default to deploying to AWS. We'd like to add more providers in the future but need help from the community in figuring our what services are equivalent to the ones we're using in AWS (Lambda for the api-side and S3/CloudFront for the web-side). - -We'll handle most of the deployment commands for you, you just need an [AWS account](https://www.serverless.com/framework/docs/providers/aws/guide/credentials#sign-up-for-an-aws-account) and your [access/secret keys](https://www.serverless.com/framework/docs/providers/aws/guide/credentials#create-an-iam-user-and-access-key) before we begin. - -### Setup - -One command will set you up with (almost) everything you need: - -```bash -yarn rw setup deploy serverless -``` - -As you'll see mentioned in the post-install instructions, you'll need to provide your AWS Access and AWS Secret Access keys. Add those to the designated places in your `.env` file: - -```bash -# .env - -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -``` - -Make sure you don't check `.env` into your repo! It's set in `.gitignore` by default, so make sure it stays that way. - -### First Deploy - -You'll need to add a special flag to the deploy command for your first deploy: - -```bash -yarn rw deploy serverless --first-run -``` - -The first time you deploy your app we'll first deploy just the API side. Once it's live we can get the URL that it's been deployed to and add that as an environment variable `API_URL` so that web side will know what it is during build-time (it needs to know where to send GraphQL and function requests). - -Half-way through the first deploy you'll be asked if you want to add the API_URL to `.env.production` (which is similar to `.env` but is only used when `NODE_ENV=production`, like when building the web and api sides for deploy). Make sure you say `Y`es at this prompt and then it will continue to deploy the web side. - -Once that command completes you should see a message including the URL of your site—open that URL and hopefully everything works as expected! - -> **Heads up** -> -> If you're getting an error trying to load data from the API side, its possible you're still pointing at your local database. -> -> Remember to add a DATABASE_URL env var to your `.env.production` file that is created, pointing at the database you want to use on your deployed site. Since your stack is on AWS, RDS might be a good option, but you might find it easier/quicker to setup databases on other providers too, such as [Railway](https://railway.app/) or [Supabase](https://supabase.com/) - -### Subsequent Deploys - -From now on you can simply run `yarn rw deploy serverless` when you're ready to deploy (which will also be much faster). - -### Environment Variables - -For local deployment (meaning you're deploying from your own machine, or another that you're in control of) you can put any ENV vars that are production-only into `.env.production`. They will override any same-named vars in `.env`. Make sure neither of these files is checked into your code repository! - -If you're setting up CI/CD and deploying from the Serverless Dashboard, you'll need to copy your required ENV vars up to your app on Serverless and then tell it where to get them from. In `api/serverless.yml` and `web/serverless.yml` look for the `provider > environment` section. You'll need to list any ENV vars here, using the `${param:VAR_NAME}` syntax, which means to get them from the Serverless Dashboard "parameters" (which is what they call environment variables, for some strange reason). - -There are even more places you can get environment variables from, check out Serverless's [Variables documentation](https://www.serverless.com/framework/docs/providers/aws/guide/variables) for more. - -### Serverless Dashboard - -> **Note:** -> Serverless Dashboard CI/CD does not support projects structured like Redwood, although they're working on it. For CD, you'll need to use something like GitHub Actions. -> -> It can still be worthwhile to integrate your project with Serverless Dashboard — you'll have features like deploy logs and monitoring, analytics, secret management, and AWS account integration. You can also [authenticate into your Serverless account within a CI context](https://www.serverless.com/framework/docs/guides/cicd/running-in-your-own-cicd). Just remember that if you do use the Dashboard to manage secrets, you'll need to use the `${param:VAR_NAME}` syntax. - -To integrate your site into the Serverless Dashboard, there are two ways: - -1. Run `yarn serverless login` and a browser *should* open asking you to allow permission. However, in our experience, this command will fail nearly 50% of the time complaining about an invalid URL. If it *does* work you can then run `yarn serverless` in both the `api` and `web` directories to link to them an existing app in the Dashboard, or you'll be prompted to create a new one. Future deploys will now be monitored on the Dashboard. -2. You can manually add the `org` and `app` lines in `api/serverless.yml` and `web/serverless.yml`. You'll see example ones commented out near the top of the file. - -### Environments Besides Production - -By default we assume you want to deploy to a production environment, but Serverless lets you deploy anywhere. They call these destinations "stages", and in Redwood "production" is the default. Check out their [Managing Staging and Environments blog post](https://www.serverless.com/blog/stages-and-environments) for details. - -Once configured, just add the stage to your deploy command: - -```bash -yarn rw deploy serverless --stage qa -``` - -### Removing Your Deploy - -In addition to creating all of the services necessary for your app to run, Serverless can also remove them (which is great when testing to avoid paying for services you're no longer using). - -You'll need to run this command in both the `api` and `web` directories: - -```bash -yarn serverless remove --stage production -``` - -Note that `production` is the default stage when you deploy with `yarn rw serverless deploy` - if you have customised this, you have to use the same stage as you deployed with! - -This will take several minutes, so grab your favorite beverage and enjoy your new $0 monthly bill! - -> Pro tip: if you get tired of typing `serverless` each time, you can use the much shorter `sls` alias: -> -> `yarn rw deploy sls` - - -## Vercel Deploy ->The following instructions assume you have read the [General Deployment Setup](#general-deployment-setup) section above. - -### Vercel tl;dr Deploy -If you simply want to experience the Vercel deployment process without a database and/or adding custom code, you can do the following: -1. create a new redwood project: `yarn create redwood-app ./vercel-deploy` -2. after your "vercel-deploy" project installation is complete, init git, commit, and add it as a new repo to GitHub, BitBucket, or GitLab -3. run the command `yarn rw setup deploy vercel` and commit and push changes -4. use the Vercel [Quick Start](https://vercel.com/#get-started) to deploy - -_If you choose this quick deploy experience, the following steps do not apply._ - - -### Redwood Project Setup -If you already have a Redwood project, proceed to the next step. - -Otherwise, we recommend experiencing the full Redwood DX via the [Redwood Tutorial](tutorial/foreword.md). Simply return to these instructions when you reach the "Deployment" section. - -### Redwood Deploy Configuration - -Complete the following two steps. Then save, commit, and push your changes. - -#### Step 1. Serverless Functions Path -Run the following CLI Command: -```shell -yarn rw setup deploy vercel -``` - -This updates your `redwood.toml` file, setting `apiUrl = "/api"`: - -#### Step 2. Database Settings -Follow the steps in the [Prisma and Database](#3-prisma-and-database) section above. _(Skip this step if your project does not require a database.)_ - -### Vercel Initial Setup and Configuration -Either [login](https://vercel.com/login) to your Vercel account and select "Import Project" or use the Vercel [quick start](https://vercel.com/#get-started). - -Then select the "Continue" button within the "From Git Repository" section: - - -Next, select the provider where your repo is hosted: GitHub, GitLab, or Bitbucket. You'll be asked to login and then provider the URL of the repository, e.g. for a GitHub repo `https://github.com/your-account/your-project.git`. Select "Continue". - -You'll then need to provide permissions for Vercel to access the repo on your hosting provider. - -### Import and Deploy your Project -Vercel will recognize your repo as a Redwood project and take care of most configuration heavy lifting. You should see the following options and, most importantly, the "Framework Preset" showing RedwoodJS. - - - -Leave the **Build and Output Settings** at the default settings (unless you know what you're doing and have very specific needs). - -In the "Environment Variables" dropdown, add `DATABASE_URL` and your app's database connection string as the value. (Or skip if not applicable.) - -> When configuring a database, you'll want to append `?connection_limit=1` to the URI. This is [recommended by Prisma](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/deployment#recommended-connection-limit) when working with relational databases in a Serverless context. For production apps, you should setup [connection pooling](https://redwoodjs.com/docs/connection-pooling). - -For example, a postgres connection string should look like `postgres://:@/?connection_limit=1` - -Finally, click the "Deploy" button. You'll hopefully see a build log without errors (warnings are fine) and end up on a screen that looks like this: - - - -Go ahead, click that "Visit" button. You’ve earned it 🎉 - -### Vercel Dashboard Settings -From the Vercel Dashboard you can access the full settings and information for your Redwood App. The default settings seem to work just fine for most Redwood projects. Do take a look around, but be sure check out the [docs as well](https://vercel.com/docs). - -From now on, each time you push code to your git repo, Vercel will automatically trigger a deploy of the new code. You can also manually redeploy if you select "Deployments", then the specific deployment from the list, and finally the "Redeploy" option from the vertical dots menu next to "Visit". diff --git a/docs/versioned_docs/version-1.0/deploy/baremetal.md b/docs/versioned_docs/version-1.0/deploy/baremetal.md deleted file mode 100644 index c5852b70549f..000000000000 --- a/docs/versioned_docs/version-1.0/deploy/baremetal.md +++ /dev/null @@ -1,326 +0,0 @@ ---- -description: Have complete control by hosting your own code ---- - -# Introduction to Baremetal - -Once you've grown beyond the confines and limitations of the cloud deployment providers, it's time to get serious: hosting your own code on big iron. Prepare for performance like you've only dreamed of! Also be prepared for IT and infrastructure responsibilities like you've only had nightmares of. - -With Redwood's Baremetal deployment option, the source (like your dev machine) will SSH into one or more remote machines and execute commands in order to update your codebase, run any database migrations and restart services. - -Deploying from a client (like your own development machine) consists of running a single command: - -First time deploy - -```bash -yarn rw deploy baremetal --first run -``` - -Subsequent deploys - -```bash -yarn rw deploy baremetal -``` - -## Deployment Lifecycle - -The baremetal deploy runs several commands in sequence. These can be customized, to an extent, and some of them skipped completely: - -1. `git pull` - gets latest code -2. `yarn install` - installs dependencies -3. `yarn rw prisma migrate deploy` - runs db migrations -3. `yarn rw prisma generate` - generates latest Prisma Client libs -4. `yarn rw dataMigrate up` - runs data migrations, ignoring them if not installed -5. `yarn rw build` - builds the web and/or api sides -6. `yarn pm2 restart [service]` - restarts the serving process(es) - -### First Run Lifecycle - -If the `--first-run` flag is specified step 6. above will be skipped and the following commands will run instead: - - `yarn pm2 start [service]` - starts the serving process(es) - - `yarn pm2 save` - saves the running services to the deploy users config file for future startup. See [Starting on Reboot](#starting-on-reboot) for further information - -> We're working on making the commands in this stack more customizable, for example `clone`ing your code instead of doing a `git pull` to avoid issues like not being able to pull because your `yarn.lock` file has changes that would be overwritten. - -## Setup - -Run the following to add the required config files: - -```bash -yarn rw setup deploy baremetal -``` - -This will create a couple of files and add a dependency or two to your `package.json`: - -1. `deploy.toml` contains server config for knowing which machines to connect to and which commands to run -2. `ecosystem.config.js` for [PM2](https://pm2.keymetrics.io/) to know what service(s) to monitor - -> **A Note about PM2 Licensing** -> -> PM2 is licensed under [AGPL v3.0](https://opensource.org/licenses/AGPL-3.0) ([here's a plain english interpretation](https://snyk.io/learn/agpl-license/)) which may have implications for your codebase. We are not lawyers, but some interpretations of the license say that if you include any software that is AGPL v3.0 then your own codebase must be released under AGPL v3.0 as well. In the case of baremetal deploys, we not including any PM2 code in your app, just counting on the PM2 daemon to monitor your web/api services to be sure they continue running. - -If you see an error from `gyp` you may need to add some additional dependencies before `yarn install` will be able to complete. See the README for `node-type` for more info: https://github.com/nodejs/node-gyp#installation - -## Configuration - -Before your first deply you'll need to add some configuration. - -### ecosystem.config.js - -By default, baremetal assumes you want to run the `yarn rw serve` command, which provides both the web and api sides. The web side will be available on port 8910 unless you update your `redwood.toml` file to make it available on another port. The default generated `ecosystem.config.js` will contain this config only, within a service called "serve": - -```jsx title="ecosystem.config.js" -module.exports = { - apps: [ - { - name: 'serve', - script: 'node_modules/.bin/rw', - args: 'serve', - instances: 'max', - exec_mode: 'cluster', - wait_ready: true, - listen_timeout: 10000, - }, - ], -} -``` - -If you follow our recommended config [below](#redwood-serves-api-nginx-serves-web-side), you could update this to only serve the api side, because the web side will be handled by [nginx](https://www.nginx.com/). That could look like: - -```jsx title="ecosystem.config.js" -module.exports = { - apps: [ - { - name: 'api', - script: 'node_modules/.bin/rw', - args: 'serve api', - instances: 'max', - exec_mode: 'cluster', - wait_ready: true, - listen_timeout: 10000, - }, - ], -} -``` - -### deploy.toml - -This file contains your server configuration: which servers to connect to and which commands to run on them. - -```toml title="deploy.toml" -[[servers]] -host = "server.com" -username = "user" -agentForward = true -sides = ["api","web"] -path = "/var/www/app" -processNames = ["serve"] -``` - -This lists a single server, providing the hostname and connection details (`username` and `agentForward`), which `sides` are hosted on this server (by default it's both web and api sides), the `path` to the app code and then which PM2 service names should be (re)started on this server. - -#### Config Options - -* `host` - hostname to the server -* `username` - the user to login as -* `password` - [optional] if you are using password authentication, include that here -* `privateKey` - [optional] if you connect with a private key, include the path to the key here -* `passphrase` - [optional] if your private key contains a passphrase, enter it here -* `agentForward` - [optional] if you have [agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding) enabled, set this to `true` and your own credentials will be used for further ssh connections from the server (like when connecting to GitHub) -* `sides` - An array of sides that will be built on this server -* `path` - The absolute path to the root of the application on the server -* `migrate` - [optional] Whether or not to run migration processes on this server, defaults to `true` -* `processNames` - An array of service names from `ecosystem.config.js` which will be (re)started on a successful deploy -* `symlinkWeb` - [optional] If using nginx or another server to serve the web side, you can have the compiled `web/dist` files symlinked in a new directory so that they are not overwritten on the next deploy. See the [Redwood Serves Api, Nginx Serves Web Side](#redwood-serves-api-nginx-serves-web-side) section for more info. - -The easiest connection method is generally to include your own public key in the server's `~/.ssh/authorized_keys` file, [enable agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding), and then set `agentForward = true` in `deploy.toml`. This will allow you to use your own credentials when pulling code from GitHub (required for private repos). Otherwise you can create a [deploy key](https://docs.github.com/en/developers/overview/managing-deploy-keys) and keep it on the server. - -> **SSH and non-interactive sessions - Possible Issues** -> -> The deployment process uses a '[non-interactive](https://tldp.org/LDP/abs/html/intandnonint.html)' ssh session to run commands on the remote server. A non-interactive session will often load a minimal amount of settings for better compatibility and speed. In some versions of Linux `.bashrc` by default does not load (by design) from a non-interactive session. This can lead to `yarn` (or other commands) not being found by the deployment script, even though they are in your path. A quick fix for this on Ubuntu is to edit the deployment users `.bashrc` and comment out the lines that stop non-interactive processing. - -```shell title=".bashrc" -# If not running interactively, don't do anything -#case $- in -# *i*) ;; -# *) return;; -#esac -``` - -#### Multiple Servers - -If you start horizontally scaling your application you may find it necessary to have the web and api sides served from different servers. The configuration files can accommodate this: - -```toml title="deploy.toml" -[[servers]] -host = "api.server.com" -username = "user" -agentForward = true -sides = ["api"] -path = "/var/www/app" -processNames = ["api"] - -[[servers]] -host = "web.server.com" -username = "user" -agentForward = true -sides = ["web"] -path = "/var/www/app" -migrate = false -processNames = ["web"] -``` - - -```jsx title="ecosystem.config.js" -module.exports = { - apps: [ - { - name: 'api', - script: 'node_modules/.bin/rw', - args: 'serve api', - instances: 'max', - exec_mode: 'cluster', - wait_ready: true, - listen_timeout: 10000, - }, - { - name: 'web', - script: 'node_modules/.bin/rw', - args: 'serve web', - instances: 'max', - exec_mode: 'cluster', - wait_ready: true, - listen_timeout: 10000, - }, - ], -} -``` - -Note the inclusion of `migrate = false` so that migrations are not run again on this server (they only need to run once and it makes sense to keep them with the api side). - -You can add as many `[[servers]]` blocks as you need. - -## Server Setup - -You will need to log into your server and `git clone` your codebase somewhere. The path to the root of your app will be set as the `path` var in `deploy.toml`. Make sure the username you will connect as in `deploy.toml` has permission to read/write/execute files in this directory. This might look something like: - -```bash -sudo mkdir -p /var/www -sudo chown myuser:myuser /var/www -git clone git@github.com:johndoe/example.git /var/www/example -``` - -You'll want to create an `.env` file containing any environment variables that are needed by the server. - -### Verification - -You should do a `yarn install` and `yarn rw build` and finally `yarn rw serve` to make sure everything works before getting the deploy process involved. If they worked for you, the deploy process should have no problem as it runs the same commands. Once `yarn rw serve` is running, make sure your processes start and are accessible (by default on port 8910): - -```bash -curl http://localhost:8910 -# or -wget http://localhost:8910 -``` - -If you don't see the content of your `web/src/index.html` file then something isn't working. You'll need to fix those issues before you can deploy. Verify the api side is responding: - -```bash -curl http://localhost:8910/.redwood/functions/graphql?query={redwood{version}} -# or -wget http://localhost:8910/.redwood/functions/graphql?query={redwood{version}} -``` - -You should see something like: - -```json -{ - "data": { - "redwood": { - "version": "1.0.0" - } - } -} -``` - -If so then your API side is up and running! The only thing left to test is that the api side has access to the database. This call would be pretty specific to your app, but assuming you have port 8910 open to the world you could simply open a browser to click around to find a page that makes a database request. - -## First Deploy - -Back on your development machine, enter your details in `deploy.toml` and then try a first deploy: - -```bash -yarn rw deploy baremetal --first-run -``` - -If there are any issues the deploy should stop and you'll see the error message printed to the console. Assume it worked, hooray! You're deployed to BAREMETAL. - -### Starting on Reboot - -The `pm2` service requires some system "hooks" to be installed so it can boot up using your systems service manager. Otherwise, your services will need to be manually started again on reboot. These steps only need to be run the first time you deploy to a machine. - -1. SSH into your server as you did for the "Server Setup". Navigate to your source folder. For example `cd /var/www/example` -2. Run the command `yarn pm2 startup`. You will see some output similar to the output below. See the output after "copy/paste the following command:"? You'll need to do just that: copy the command starting with `sudo` and then paste and execute it. *Note* this command uses `sudo` so you'll need the root password to the machine in order for it to complete successfully. - -> The below text is an *example* output. Yours will be different - -```bash -deploy@redwood:/var/www/my-app$ yarn pm2 startup -[PM2] Init System found: systemd -[PM2] To setup the Startup Script, copy/paste the following command: -sudo env PATH=$PATH:/home/deploy/.nvm/versions/node/v17.8.0/bin /var/www/my-app/node_modules/pm2/bin/pm2 startup systemd -u deploy --hp /home/deploy -``` - - -In this example, you would copy `sudo env PATH=$PATH:/home/deploy/.nvm/versions/node/v17.8.0/bin /var/www/my-app/node_modules/pm2/bin/pm2 startup systemd -u deploy --hp /home/deploy` and run it. - -### Customizing the Deploy - -If you want to speed things up you can skip one or more steps during the deploy. For example, if you have no database migrations, you can skip those steps completely: - -```bash -yarn rw deploy baremetal --no-migrate -``` - -Run `yarn rw deploy baremetal --help` for the full list of flags. You can set them as `--migrate=false` or use the `--no-migrate` variant. - -## Example Configurations - -The default configuration, which requires the least amount of manual configuration, is to serve both the web and api sides, with the web side being bound to port 8910. This isn't really feasible for a general web app which should be available on port 80 (for HTTP) and/or port 443 (for HTTPS). Here are some custom configs that would enable - -### Redwood Serves Web and Api Sides, Bind to Port 80 - -This is almost as easy as the default configuration, you just need to tell Redwood to bind to port 80. However, most *nix distributions will not allow a process to bind to ports lower than 1024 without root/sudo permissions. There is a command you can run to allow access to a specific binary (node, in this case) to bind to one of those ports anyway. - -#### redwood.toml - -Update the `[web]` port: - -```toml title="redwood.toml" -[web] - title = "My Application" - // highlight-next-line - port = 80 - apiUrl = "/.netlify/functions" -[api] - port = 8911 -[browser] - open = true -``` - -#### Allow Binding to Port 80 - -Use the [setcap](https://man7.org/linux/man-pages/man7/capabilities.7.html) utility to provide access to lower ports by a given process: - -```bash -sudo setcap CAP_NET_BIND_SERVICE=+eip $(which node) -``` - -Now restart your service and it should be available on port 80: - -```bash -yarn pm2 restart serve -``` - -### Redwood Serves Api, Nginx Serves Web Side - -Coming soon! diff --git a/docs/versioned_docs/version-1.0/deploy/serverless.md b/docs/versioned_docs/version-1.0/deploy/serverless.md deleted file mode 100644 index eb9eaec88048..000000000000 --- a/docs/versioned_docs/version-1.0/deploy/serverless.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -description: Deploy to AWS with Serverless Framework ---- - -# Deploy to AWS with Serverless Framework - ->The following instructions assume you have read the [General Deployment Setup](#general-deployment-setup) section above. - -Yes, the name is confusing, but Serverless provides a very interesting option—deploy to your own cloud service account and skip the middleman entirely! By default, Serverless just orchestrates starting up services in your cloud provider of choice and pushing your code up to them. Any bill you receive is from your hosting provider (although many offer a generous free tier). You can optionally use the [Serverless Dashboard](https://www.serverless.com/dashboard/) to monitor your deploys and setup CI/CD to automatically deploy when pushing to your repo of choice. If you don't setup CI/CD you actually deploy from your development machine (or another designated machine you've setup to do the deployment). - -Currently we default to deploying to AWS. We'd like to add more providers in the future but need help from the community in figuring our what services are equivalent to the ones we're using in AWS (Lambda for the api-side and S3/CloudFront for the web-side). - -We'll handle most of the deployment commands for you, you just need an [AWS account](https://www.serverless.com/framework/docs/providers/aws/guide/credentials#sign-up-for-an-aws-account) and your [access/secret keys](https://www.serverless.com/framework/docs/providers/aws/guide/credentials#create-an-iam-user-and-access-key) before we begin. - -## Setup - -One command will set you up with (almost) everything you need: - -```bash -yarn rw setup deploy serverless -``` - -As you'll see mentioned in the post-install instructions, you'll need to provide your AWS Access and AWS Secret Access keys. Add those to the designated places in your `.env` file: - -```bash -# .env - -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -``` - -Make sure you don't check `.env` into your repo! It's set in `.gitignore` by default, so make sure it stays that way. - -## First Deploy - -You'll need to add a special flag to the deploy command for your first deploy: - -```bash -yarn rw deploy serverless --first-run -``` - -The first time you deploy your app we'll first deploy just the API side. Once it's live we can get the URL that it's been deployed to and add that as an environment variable `API_URL` so that web side will know what it is during build-time (it needs to know where to send GraphQL and function requests). - -Half-way through the first deploy you'll be asked if you want to add the API_URL to `.env.production` (which is similar to `.env` but is only used when `NODE_ENV=production`, like when building the web and api sides for deploy). Make sure you say `Y`es at this prompt and then it will continue to deploy the web side. - -Once that command completes you should see a message including the URL of your site—open that URL and hopefully everything works as expected! - -> **Heads up** -> -> If you're getting an error trying to load data from the API side, its possible you're still pointing at your local database. -> -> Remember to add a DATABASE_URL env var to your `.env.production` file that is created, pointing at the database you want to use on your deployed site. Since your stack is on AWS, RDS might be a good option, but you might find it easier/quicker to setup databases on other providers too, such as [Railway](https://railway.app/) or [Supabase](https://supabase.com/) - -## Subsequent Deploys - -From now on you can simply run `yarn rw deploy serverless` when you're ready to deploy (which will also be much faster). - -## Environment Variables - -For local deployment (meaning you're deploying from your own machine, or another that you're in control of) you can put any ENV vars that are production-only into `.env.production`. They will override any same-named vars in `.env`. Make sure neither of these files is checked into your code repository! - -If you're setting up CI/CD and deploying from the Serverless Dashboard, you'll need to copy your required ENV vars up to your app on Serverless and then tell it where to get them from. In `api/serverless.yml` and `web/serverless.yml` look for the `provider > environment` section. You'll need to list any ENV vars here, using the `${param:VAR_NAME}` syntax, which means to get them from the Serverless Dashboard "parameters" (which is what they call environment variables, for some strange reason). - -There are even more places you can get environment variables from, check out Serverless's [Variables documentation](https://www.serverless.com/framework/docs/providers/aws/guide/variables) for more. - -## Serverless Dashboard - -> **Note:** -> Serverless Dashboard CI/CD does not support projects structured like Redwood, although they're working on it. For CD, you'll need to use something like GitHub Actions. -> -> It can still be worthwhile to integrate your project with Serverless Dashboard — you'll have features like deploy logs and monitoring, analytics, secret management, and AWS account integration. You can also [authenticate into your Serverless account within a CI context](https://www.serverless.com/framework/docs/guides/cicd/running-in-your-own-cicd). Just remember that if you do use the Dashboard to manage secrets, you'll need to use the `${param:VAR_NAME}` syntax. - -To integrate your site into the Serverless Dashboard, there are two ways: - -1. Run `yarn serverless login` and a browser *should* open asking you to allow permission. However, in our experience, this command will fail nearly 50% of the time complaining about an invalid URL. If it *does* work you can then run `yarn serverless` in both the `api` and `web` directories to link to them an existing app in the Dashboard, or you'll be prompted to create a new one. Future deploys will now be monitored on the Dashboard. -2. You can manually add the `org` and `app` lines in `api/serverless.yml` and `web/serverless.yml`. You'll see example ones commented out near the top of the file. - -## Environments Besides Production - -By default we assume you want to deploy to a production environment, but Serverless lets you deploy anywhere. They call these destinations "stages", and in Redwood "production" is the default. Check out their [Managing Staging and Environments blog post](https://www.serverless.com/blog/stages-and-environments) for details. - -Once configured, just add the stage to your deploy command: - -```bash -yarn rw deploy serverless --stage qa -``` - -## Removing Your Deploy - -In addition to creating all of the services necessary for your app to run, Serverless can also remove them (which is great when testing to avoid paying for services you're no longer using). - -You'll need to run this command in both the `api` and `web` directories: - -```bash -yarn serverless remove --stage production -``` - -Note that `production` is the default stage when you deploy with `yarn rw serverless deploy` - if you have customised this, you have to use the same stage as you deployed with! - -This will take several minutes, so grab your favorite beverage and enjoy your new $0 monthly bill! - -> Pro tip: if you get tired of typing `serverless` each time, you can use the much shorter `sls` alias: -> -> `yarn rw deploy sls` diff --git a/docs/versioned_docs/version-1.0/deploy/vercel.md b/docs/versioned_docs/version-1.0/deploy/vercel.md deleted file mode 100644 index 09e554ec576f..000000000000 --- a/docs/versioned_docs/version-1.0/deploy/vercel.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -description: Deploy serverless in an instant with Vercel ---- - -# Deploy to Vercel - ->The following instructions assume you have read the [General Deployment Setup](#general-deployment-setup) section above. - -## Vercel tl;dr Deploy - -If you simply want to experience the Vercel deployment process without a database and/or adding custom code, you can do the following: -1. create a new redwood project: `yarn create redwood-app ./vercel-deploy` -2. after your "vercel-deploy" project installation is complete, init git, commit, and add it as a new repo to GitHub, BitBucket, or GitLab -3. run the command `yarn rw setup deploy vercel` and commit and push changes -4. use the Vercel [Quick Start](https://vercel.com/#get-started) to deploy - -_If you choose this quick deploy experience, the following steps do not apply._ - -## Redwood Project Setup - -If you already have a Redwood project, proceed to the next step. - -Otherwise, we recommend experiencing the full Redwood DX via the [Redwood Tutorial](tutorial/foreword.md). Simply return to these instructions when you reach the "Deployment" section. - -## Redwood Deploy Configuration - -Complete the following two steps. Then save, commit, and push your changes. - -### Step 1. Serverless Functions Path - -Run the following CLI Command: -```shell -yarn rw setup deploy vercel -``` - -This updates your `redwood.toml` file, setting `apiUrl = "/api"`: - -### Step 2. Database Settings - -Follow the steps in the [Prisma and Database](#3-prisma-and-database) section above. _(Skip this step if your project does not require a database.)_ - -### Vercel Initial Setup and Configuration -Either [login](https://vercel.com/login) to your Vercel account and select "Import Project" or use the Vercel [quick start](https://vercel.com/#get-started). - -Then select the "Continue" button within the "From Git Repository" section: - - -Next, select the provider where your repo is hosted: GitHub, GitLab, or Bitbucket. You'll be asked to login and then provider the URL of the repository, e.g. for a GitHub repo `https://github.com/your-account/your-project.git`. Select "Continue". - -You'll then need to provide permissions for Vercel to access the repo on your hosting provider. - -### Import and Deploy your Project -Vercel will recognize your repo as a Redwood project and take care of most configuration heavy lifting. You should see the following options and, most importantly, the "Framework Preset" showing RedwoodJS. - - - -Leave the **Build and Output Settings** at the default settings (unless you know what you're doing and have very specific needs). - -In the "Environment Variables" dropdown, add `DATABASE_URL` and your app's database connection string as the value. (Or skip if not applicable.) - -> When configuring a database, you'll want to append `?connection_limit=1` to the URI. This is [recommended by Prisma](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/deployment#recommended-connection-limit) when working with relational databases in a Serverless context. For production apps, you should setup [connection pooling](https://redwoodjs.com/docs/connection-pooling). - -For example, a postgres connection string should look like `postgres://:@/?connection_limit=1` - -Finally, click the "Deploy" button. You'll hopefully see a build log without errors (warnings are fine) and end up on a screen that looks like this: - - - -Go ahead, click that "Visit" button. You’ve earned it 🎉 - -## Vercel Dashboard Settings - -From the Vercel Dashboard you can access the full settings and information for your Redwood App. The default settings seem to work just fine for most Redwood projects. Do take a look around, but be sure check out the [docs as well](https://vercel.com/docs). - -From now on, each time you push code to your git repo, Vercel will automatically trigger a deploy of the new code. You can also manually redeploy if you select "Deployments", then the specific deployment from the list, and finally the "Redeploy" option from the vertical dots menu next to "Visit". diff --git a/docs/versioned_docs/version-1.0/directives.md b/docs/versioned_docs/version-1.0/directives.md deleted file mode 100644 index 2f0046ab5c53..000000000000 --- a/docs/versioned_docs/version-1.0/directives.md +++ /dev/null @@ -1,656 +0,0 @@ -# Directives - -Redwood Directives are a powerful feature, supercharging your GraphQL-backed Services. - -You can think of directives like "middleware" that let you run reusable code during GraphQL execution to perform tasks like authentication and formatting. -Redwood uses them to make it a snap to protect your API Services from unauthorized access. -Here we call those types of directives **Validators**. - -You can also use them to transform the output of your query result to modify string values, format dates, shield sensitive data, and more! -We call those types of directives **Transformers**. - -You'll recognize a directive as being 1) preceded by `@` (e.g. `@myDirective`) and 2) declared alongside a field: - -```ts -type Bar { - name: String! @myDirective -} -``` - -or a Query or a Mutation: - -```ts -type Query { - bars: [Bar!]! @myDirective -} - -type Mutation { - createBar(input: CreateBarInput!): Bar! @myDirective -} -``` - -You can also define arguments that can be extracted and used when evaluating the directive: - -```ts -type Bar { - field: String! @myDirective(roles: ["ADMIN"]) -} -``` - -or a Query or Mutation: - -```ts -type Query { - bars: [Bar!]! @myDirective(roles: ["ADMIN"]) -} -``` - -You can also use directives on relations: - -```ts -type Baz { - name: String! -} - -type Bar { - name: String! - bazzes: [Baz]! @myDirective -} -``` - -There are many ways to write directives using GraphQL tools and libraries. Believe us, it can get complicated fast. - -But, don't fret: Redwood provides an easy and ergonomic way to generate and write your own directives so that you can focus on the implementation logic and not the GraphQL plumbing. - -## What is a Redwood Directive? - -Redwood directives are purposeful. -They come in two flavors: **Validators** and **Transformers**. - -Whatever flavor of directive you want, all Redwood directives must have the following properties: - -- be in the `api/src/directives/{directiveName}` directory where `directiveName` is the directive directory -- must have a file named `{directiveName}.{js,ts}` (e.g. `maskedEmail.ts`) -- must export a `schema` and implement either a `validate` or `transform` function - -### Understanding the Directive Flow - -Since it helps to know a little about the GraphQL phases—specifically the Execution phase—and how Redwood Directives fit in the data-fetching and authentication flow, let's have a quick look at some diagrams. - -First, we see the built-in `@requireAuth` Validator directive that can allow or deny access to a Service (a.k.a. a resolver) based on Redwood authentication. -In this example, the `post(id: Int!)` query is protected using the `@requireAuth` directive. - -If the request's context has a `currentUser` and the app's `auth.{js|ts}` determines it `isAuthenticated()`, then the execution phase proceeds to get resolved (for example, the `post({ id })` Service is executed and queries the database using Prisma) and returns the data in the resulting response when execution is done. - -![require-auth-directive](https://user-images.githubusercontent.com/1051633/135320891-34dc06fc-b600-4c76-8a35-86bf42c7f179.png) - -In this second example, we add the Transformer directive `@welcome` to the `title` field on `Post` in the SDL. - -The GraphQL Execution phase proceeds the same as the prior example (because the `post` query is still protected and we'll want to fetch the user's name) and then the `title` field is resolved based on the data fetch query in the service. - -Finally after execution is done, then the directive can inspect the `resolvedValue` (here "Welcome to the blog!") and replace the value by inserting the current user's name—"Welcome, Tom, to the blog!" - -![welcome-directive](https://user-images.githubusercontent.com/1051633/135320906-5e2d639d-13a1-4aaf-85bf-98529822d244.png) - -### Validators - -Validators integrate with Redwood's authentication to evaluate whether or not a field, query, or mutation is permitted—that is, if the request context's `currentUser` is authenticated or belongs to one of the permitted roles. - -Validators should throw an Error such as `AuthenticationError` or `ForbiddenError` to deny access and simply return to allow. - -Here the `@isSubscriber` validator directive checks if the currentUser exists (and therefore is authenticated) and whether or not they have the `SUBSCRIBER` role. If they don't, then access is denied by throwing an error. - -```ts -import { - AuthenticationError, - ForbiddenError, - createValidatorDirective, - ValidatorDirectiveFunc, -} from '@redwoodjs/graphql-server' -import { hasRole } from 'src/lib/auth' - -export const schema = gql` - directive @isSubscriber on FIELD_DEFINITION -` - -const validate: ValidatorDirectiveFunc = ({ context }) => { - if (!context.currentUser) { - throw new AuthenticationError("You don't have permission to do that.") - } - - if (!context.currentUser.roles?.includes('SUBSCRIBER')) { - throw new ForbiddenError("You don't have access to do that.") - } -} - -const isSubscriber = createValidatorDirective(schema, validate) - -export default isSubscriber -``` - -Since validator directives can access arguments (such as `roles`), you can quickly provide RBAC (Role-based Access Control) to fields, queries and mutations. - -```ts -import gql from 'graphql-tag' - -import { createValidatorDirective } from '@redwoodjs/graphql-server' - -import { requireAuth as applicationRequireAuth } from 'src/lib/auth' -import { logger } from 'src/lib/logger' - -export const schema = gql` - directive @requireAuth(roles: [String]) on FIELD_DEFINITION -` - -const validate = ({ directiveArgs }) => { - const { roles } = directiveArgs - - applicationRequireAuth({ roles }) -} - -const requireAuth = createValidatorDirective(schema, validate) - -export default requireAuth -``` - -All Redwood apps come with two built-in validator directives: `@requireAuth` and `@skipAuth`. -The `@requireAuth` directive takes optional roles. -You may use these to protect against unwanted GraphQL access to your data. -Or explicitly allow public access. - -> **Note:** Validators evaluate prior to resolving the field value, so you cannot modify the value and any return value is ignored. - -### Transformers - -Transformers can access the resolved field value to modify and then replace it in the response. -Transformers apply to both single fields (such as a `User`'s `email`) and collections (such as a set of `Posts` that belong to `User`s) or is the result of a query. As such, Transformers cannot be applied to Mutations. - -In the first case of a single field, the directive would return the modified field value. In the latter case, the directive could iterate each `Post` and modify the `title` in each. In all cases, the directive **must** return the same expected "shape" of the data the SDL expects. - -> **Note:** you can chain directives to first validate and then transform, such as `@requireAuth @maskedEmail`. Or even combine transformations to cascade formatting a value (you could use `@uppercase` together with `@truncate` to uppercase a title and shorten to 10 characters). - -Since transformer directives can access arguments (such as `roles` or `maxLength`) you may fetch those values and use them when applying (or to check if you even should apply) your transformation. - -That means that a transformer directive could consider the `permittedRoles` in: - -```ts -type user { - email: String! @maskedEmail(permittedRoles: ["ADMIN"]) -} -``` - -and if the `currentUser` is an `ADMIN`, then skip the masking transform and simply return the original resolved field value: - -```jsx -// ./api/directives/maskedEmail.directive.js -import { createTransformerDirective, TransformerDirectiveFunc } from '@redwoodjs/graphql-server' - -export const schema = gql` - directive @maskedEmail on FIELD_DEFINITION -` - -const transform: TransformerDirectiveFunc = ({ context, resolvedValue }) => { - return resolvedValue.replace(/[a-zA-Z0-9]/i, '*') -} - -const maskedEmail = createTransformerDirective(schema, transform) - -export default maskedEmail -``` - -and you would use it in your SDLs like this: - -```graphql -type UserExample { - id: Int! - email: String! @maskedEmail # 👈 will replace alphanumeric characters with asterisks in the response! - name: String -} -``` - -### Where can I use a Redwood Directive? - -A directive can only appear in certain locations in a GraphQL schema or operation. These locations are listed in the directive's definition. - -In the example below, the `@maskedEmail` example, the directive can only appear in the `FIELD_DEFINITION` location. - -An example of a `FIELD_DEFINITION` location is a field that exists on a `Type`: - -```graphql -type UserExample { - id: Int! - email: String! @requireAuth - name: String @maskedEmail # 👈 will maskedEmail name in the response! -} - -type Query { - userExamples: [UserExample!]! @requireAuth 👈 will enforce auth when fetching all users - userExamples(id: Int!): UserExample @requireAuth 👈 will enforce auth when fetching a us -} -``` - -> **Note**: Even though GraphQL supports `FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE` locations, RedwoodDirectives can **only** be declared on a `FIELD_DEFINITION` — that is, you **cannot** declare a directive in an `Input type`: -> -> ```graphql -> input UserExampleInput { -> email: String! @maskedEmail # 👈 🙅 not allowed on an input -> name: String! @requireAuth # 👈 🙅 also not allowed on an input -> } -> ``` - -## When Should I Use a Redwood Directive? - -As noted in the [GraphQL spec](https://graphql.org/learn/queries/#directives): - -> Directives can be useful to get out of situations where you otherwise would need to do string manipulation to add and remove fields in your query. Server implementations may also add experimental features by defining completely new directives. - -Here's a helpful guide for deciding when you should use one of Redwood's Validator or Transformer directives: - -| | Use | Directive | Custom? | Type | -| --- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------ | -| ✅ | Check if the request is authenticated? | `@requireAuth` | Built-in | Validator | -| ✅ | Check if the user belongs to a role? | `@requireAuth(roles: ["AUTHOR"])` | Built-in | Validator | -| ✅ | Only allow admins to see emails, but others get a masked value like "###@######.###" | `@maskedEmail(roles: ["ADMIN"])` | Custom | Transformer | -| 🙅 | Know if the logged in user can edit the record, and/or values | N/A - Instead do this check in your service | -| 🙅 | Is my input a valid email address format? | N/A - Instead do this check in your service using [Service Validations](services.md#service-validations) or consider [GraphQL Scalars](https://www.graphql-scalars.dev) | -| 🙅 | I want to remove a field from the response for data filtering; for example, do not include the title of the post | `@skip(if: true )` or `@include(if: false)` | Instead use [core directives](https://graphql.org/learn/queries/#directives) on the GraphQL client query, not the SDL | Core GraphQL | - -## Combining, Chaining and Cascading Directives - -Now that you've seen what Validator and Transformer directives look like and where and when you may use them, you may wonder: can I use them together? Can I transform the result of a transformer? - -The answer is: yes—yes you can! - -### Combine Directives on a Query and a Type Field - -Let's say you want to only allow logged-in users to be able to query `User` details and you only want un-redacted email addresses to be shown to ADMINs. - -You can apply the `@requireAuth` directive to the `user(id: Int!)` query so you have to be logged in. -Then, you can compose a `@maskedEmail` directive that checks the logged-in user's role membership and if they're not an ADMIN, mask the email address: - -```ts - type User { - id: Int! - name: String! - email: String! @maskedEmail(role: "ADMIN") - createdAt: DateTime! - } - - type Query { - user(id: Int!): User @requireAuth - } -``` - -Or, let's say I want to only allow logged in users to be able to query User details. - -But, I only want ADMIN users to be able to query and fetch the email address. - -I can apply the `@requireAuth` directive to the `user(id: Int!)` query so I have to be logged in. - -And, I can apply the `@requireAuth` directive to the `email` field with a role argument. - -```ts - type User { - id: Int! - name: String! - email: String! @requireAuth(role: "ADMIN") - createdAt: DateTime! - } - - type Query { - user(id: Int!): User @requireAuth - } -``` - -Now, if a user who is not an ADMIN queries: - -```ts -query user(id: 1) { - id - name - createdAt -} -``` - -They will get a result. - -But, if they try to query: - -```ts -query user(id: 1) { - id - name - email - createdAt -} -``` - -They will be forbidden from even making the request. - -### Chaining a Validator and a Transformer - -Similar to the prior example, you may want to chain directives, but the transform doesn't consider authentication or role membership. - -For example, here we ensure that anyone trying to query a User and fetch the email must be authenticated. - -And then, if they are, apply a mask to the email field. - -```ts - type User { - id: Int! - name: String! - email: String! @requireAuth @maskedEmail - createdAt: DateTime! - } -``` - -### Cascade Transformers - -Maybe you want to apply multiple field formatting? - -If your request event headers includes geographic or timezone info, you could compose a custom Transformer directive called `@localTimezone` could inspect the header value and convert the `createdAt` from UTC to local time -- something often done in the browser. - -Then, you can chain the `@dateFormat` Transformer, to just return the date portion of the timestamp -- and not the time. - -```ts - type User { - id: Int! - name: String! - email: String! - createdAt: DateTime! @localTimezone @dateFormat - } -``` - -> **Note**: These directives could be alternatively be implemented as "operation directives" so the client can use them on a query instead of the schema-level. These such directives are a potential future Redwood directive feature. - -## GraphQL Handler Setup - -Redwood makes it easy to code, organize, and map your directives into your GraphQL schema. -Simply add them to the `directives` directory and the `createGraphQLHandler` does all the work. - -You simply add them to the `directives` directory and the `createGraphQLHandler` will do all the work. - -> **Note**: Redwood has a generator that will do all the heavy lifting setup for you! - -```ts -// api/src/functions/graphql.ts - -import { createGraphQLHandler } from '@redwoodjs/graphql-server' - -import directives from 'src/directives/**/*.{js,ts}' // 👈 directives live here -import sdls from 'src/graphql/**/*.sdl.{js,ts}' -import services from 'src/services/**/*.{js,ts}' - -import { db } from 'src/lib/db' -import { logger } from 'src/lib/logger' - -export const handler = createGraphQLHandler({ - loggerConfig: { logger, options: {} }, - directives, // 👈 directives are added to the schema here - sdls, - services, - onException: () => { - // Disconnect from your database with an unhandled exception. - db.$disconnect() - }, -}) -``` - -## Secure by Default with Built-in Directives - -By default, your GraphQL endpoint is open to the world. - -That means anyone can request any query and invoke any Mutation. -Whatever types and fields are defined in your SDL is data that anyone can access. - -But Redwood encourages being secure by default by defaulting all queries and mutations to have the `@requireAuth` directive when generating SDL or a service. -When your app builds and your server starts up, Redwood checks that **all** queries and mutations have `@requireAuth`, `@skipAuth` or a custom directive applied. - -If not, then your build will fail: - -```terminal - ✖ Verifying graphql schema... - Building API... - Cleaning Web... - Building Web... - Prerendering Web... -You must specify one of @requireAuth, @skipAuth or a custom directive for -- contacts Query -- posts Query -- post Query -- updatePost Mutation -- deletePost Mutation -``` - -or your server won't startup and you should see that "Schema validation failed": - -```terminal -gen | Generating TypeScript definitions and GraphQL schemas... -gen | 47 files generated -api | Building... Took 593 ms -api | [GQL Server Error] - Schema validation failed -api | ---------------------------------------- -api | You must specify one of @requireAuth, @skipAuth or a custom directive for -api | - posts Query -api | - createPost Mutation -api | - updatePost Mutation -api | - deletePost Mutation -``` - -To correct, just add the appropriate directive to your queries and mutations. - -If not, then your build will fail and your server won't startup. - -### @requireAuth - -It's your responsibility to implement the `requireAuth()` function in your app's `api/src/lib/auth.{js|ts}` to check if the user is properly authenticated and/or has the expected role membership. - -The `@requireAuth` directive will call the `requireAuth()` function to determine if the user is authenticated or not. - -```ts -// api/src/lib/auth.ts - -// ... - -export const isAuthenticated = (): boolean => { - return true // 👈 replace with the appropriate check -} - -// ... - -export const requireAuth = ({ roles }: { roles: AllowedRoles }) => { - if (isAuthenticated()) { - throw new AuthenticationError("You don't have permission to do that.") - } - - if (!hasRole({ roles })) { - throw new ForbiddenError("You don't have access to do that.") - } -} -``` - -> **Note**: The `auth.ts` file here is the stub for a new RedwoodJS app. Once you have setup auth with your provider, this will enforce a proper authentication check. - -### @skipAuth - -If, however, you want your query or mutation to be public, then simply use `@skipAuth`. - -## Custom Directives - -Want to write your own directive? You can of course! -Just generate one using the Redwood CLI; it takes care of the boilerplate and even gives you a handy test! - -### Generators - -When using the `yarn redwood generate` command, -you'll be presented with a choice of creating a Validator or a Transformer directive. - -```bash -yarn redwood generate directive myDirective - -? What type of directive would you like to generate? › - Use arrow-keys. Return to submit. -❯ Validator - Implement a validation: throw an error if criteria not met to stop execution - Transformer - Modify values of fields or query responses -``` - -> **Note:** You can pass the `--type` flag with either `validator` or `transformer` to create the desired directive type. - -After picking the directive type, the files will be created in your `api/src/directives` directory: - -```terminal - ✔ Generating directive file ... - ✔ Successfully wrote file `./api/src/directives/myDirective/myDirective.test.ts` - ✔ Successfully wrote file `./api/src/directives/myDirective/myDirective.ts` - ✔ Generating TypeScript definitions and GraphQL schemas ... - ✔ Next steps... - - After modifying your directive, you can add it to your SDLs e.g.: - // example todo.sdl.js - # Option A: Add it to a field - type Todo { - id: Int! - body: String! @myDirective - } - - # Option B: Add it to query/mutation - type Query { - todos: [Todo] @myDirective - } -``` - -### Validator - -Let's create a `@isSubscriber` directive that checks roles to see if the user is a subscriber. - -```terminal -yarn rw g directive isSubscriber --type validator -``` - -Next, implement your validation logic in the directive's `validate` function. - -Validator directives don't have access to the field value, (i.e. they're called before resolving the value). But they do have access to the `context` and `directiveArgs`. -They can be async or sync. -And if you want to stop executing (because of insufficient permissions for example), throw an error. -The return value is ignored - -An example of `directiveArgs` is the `roles` argument in the directive `requireAuth(roles: "ADMIN")` - -```ts -const validate: ValidatorDirectiveFunc = ({ context, directiveArgs }) => { - // You can also modify your directive to take arguments - // and use the directiveArgs object provided to this function to get values - logger.debug(directiveArgs, 'directiveArgs in isSubscriber directive') - - throw new Error('Implementation missing for isSubscriber') -} -``` - -Here we can access the `context` parameter and then check to see if the `currentUser` is authenticated and if they belong to the `SUBSCRIBER` role: - -```ts -// /api/src/directives/isSubscriber/isSubscriber.ts -// ... - -const validate: ValidatorDirectiveFunc = ({ context }) => { - if (!context.currentUser)) { - throw new AuthenticationError("You don't have permission to do that.") - } - - if (!context.currentUser.roles?.includes('SUBSCRIBER')) { - throw new ForbiddenError("You don't have access to do that.") - } -} -``` - -#### Writing Validator Tests - -When writing a Validator directive test, you'll want to: - -- ensure the directive is named consistently and correctly so the directive name maps properly when validating -- confirm that the directive throws an error when invalid. The Validator directive should always have a reason to throw an error - -Since we stub out the `Error('Implementation missing for isSubscriber')` case when generating the Validator directive, these tests should pass. -But once you begin implementing the validate logic, it's on you to update appropriately. - -```ts -import { mockRedwoodDirective, getDirectiveName } from '@redwoodjs/testing/api' - -import isSubscriber from './isSubscriber' - -describe('isSubscriber directive', () => { - it('declares the directive sdl as schema, with the correct name', () => { - expect(isSubscriber.schema).toBeTruthy() - expect(getDirectiveName(isSubscriber.schema)).toBe('isSubscriber') - }) - - it('has a isSubscriber throws an error if validation does not pass', () => { - const mockExecution = mockRedwoodDirective(isSubscriber, {}) - - expect(mockExecution).toThrowError('Implementation missing for isSubscriber') - }) -}) -``` - -### Transformer - -Let's create a `@maskedEmail` directive that checks roles to see if the user should see the complete email address or if it should be obfuscated from prying eyes: - -```terminal -yarn rw g directive maskedEmail --type transformer -``` - -Next, implement your validation logic in the directive's `transform` function. - -Transformer directives provide `context` and `resolvedValue` parameters and run **after** resolving the value. -Transformer directives **must** be synchronous, and return a value. -You can throw an error, if you want to stop executing, but note that the value has already been resolved. - -Take note of the `resolvedValue`: - -```ts -const transform: TransformerDirectiveFunc = ({ context, resolvedValue }) => { - return resolvedValue.replace('foo', 'bar') -} -``` - -It contains the value of the field on which the directive was placed. Here, `email`. -So the `resolvedValue` will be the value of the email property in the User model, the "original value" so-to-speak. - -When you return a value from the `transform` function, just return a modified value and that will be returned as the result and replace the `email` value in the response. - -> 🛎️ **Important** -> -> You must return a value of the same type. So, if your `resolvedValue` is a `String`, return a `String`. If it's a `Date`, return a `Date`. Otherwise, your data will not match the SDL Type. - -#### Writing Transformer Tests - -When writing a Transformer directive test, you'll want to: - -- ensure the directive is named consistently and correctly so the directive name maps properly when transforming -- confirm that the directive returns a value and that it's the expected transformed value - -Since we stub out and mock the `mockedResolvedValue` when generating the Transformer directive, these tests should pass. - -Here we mock the value `foo` and, since the generated `transform` function replaces `foo` with `bar`, we expect that after execution, the returned value will be `bar`. -But once you begin implementing the validate logic, it's on you to update appropriately. - -```ts -import { mockRedwoodDirective, getDirectiveName } from '@redwoodjs/testing/api' - -import maskedEmail from './maskedEmail' - -describe('maskedEmail directive', () => { - it('declares the directive sdl as schema, with the correct name', () => { - expect(maskedEmail.schema).toBeTruthy() - expect(getDirectiveName(maskedEmail.schema)).toBe('maskedEmail') - }) - - it('has a maskedEmail implementation transforms the value', () => { - const mockExecution = mockRedwoodDirective(maskedEmail, { - mockedResolvedValue: 'foo', - }) - - expect(mockExecution()).toBe('bar') - }) -}) -``` diff --git a/docs/versioned_docs/version-1.0/environment-variables.md b/docs/versioned_docs/version-1.0/environment-variables.md deleted file mode 100644 index 511536a51ee7..000000000000 --- a/docs/versioned_docs/version-1.0/environment-variables.md +++ /dev/null @@ -1,140 +0,0 @@ -# Environment Variables - -You can provide environment variables to each side of your Redwood app in different ways, depending on each Side's target, and whether you're in development or production. - -> Right now, Redwood apps have two fixed Sides, API and Web, that have each have a single target, nodejs and browser respectively. - -## Generally - -Redwood apps use [dotenv](https://github.com/motdotla/dotenv) to load vars from your `.env` file into `process.env`. -For a reference on dotenv syntax, see the dotenv README's [Rules](https://github.com/motdotla/dotenv#rules) section. - -> Technically, we use [dotenv-defaults](https://github.com/mrsteele/dotenv-defaults), which is how we also supply and load `.env.defaults`. - - - -Redwood also configures Webpack with `dotenv-webpack`, so that all references to `process.env` vars on the Web side will be replaced with the variable's actual value at built-time. More on this in [Web](#Web). - -## Web - -### Including environment variables -> **Heads Up:** for Web to access environment variables in production, you _must_ configure one of the options below. -> -> Redwood recommends **Option 1: `redwood.toml`** as it is the most robust. - -In production, you can get environment variables to the Web Side either by - -1. adding to `redwood.toml` via the `includeEnvironmentVariables` array, or -2. prefixing with `REDWOOD_ENV_` - -Just like for the API Side, you'll also have to set them up with your provider. - -#### Option 1: includeEnvironmentVariables in redwood.toml - -For Example: - -```toml -// redwood.toml - -[web] - includeEnvironmentVariables = ['SECRET_API_KEY', 'ANOTHER_ONE'] -``` - -By adding environment variables to this array, they'll be available to Web in production via `process.env.SECRET_API_KEY`. This means that if you have an environment variable like `process.env.SECRET_API_KEY` Redwood removes and replaces it with its _actual_ value. - -Note: if someone inspects your site's source, _they could see your `REDWOOD_ENV_SECRET_API_KEY` in plain text._ This is a limitation of delivering static JS and HTML to the browser. - -#### Option 2: Prefixing with REDWOOD*ENV* - -In `.env`, if you prefix your environment variables with `REDWOOD_ENV_`, they'll be available via `process.env.REDWOOD_ENV_MY_VAR_NAME`, and will be dynamically replaced at built-time. - -Like the option above, these are also removed and replaced with the _actual value_ during build in order to be available in production. - - -### Accessing API URLs - -Redwood automatically makes your API URL configurations from the web section of your `redwood.toml` available globally. -They're accessible via the `window` or `global` objects. -For example, `global.RWJS_API_GRAPHQL_URL` gives you the URL for your graphql endpoint. - -The toml values are mapped as follows: - -| `redwood.toml` key | Available globally as | Description | -| ------------------ | ----------------------------- | ---------------------------------------- | -| `apiUrl` | `global.RWJS_API_URL` | URL or absolute path to your api-server | -| `apiGraphQLUrl` | `global.RWJS_API_GRAPHQL_URL` | URL or absolute path to GraphQL function | -| `apiDbAuthUrl` | `global.RWJS_API_DBAUTH_URL` | URL or absolute path to DbAuth function | - -See the [redwood.toml reference](app-configuration-redwood-toml.md#api-paths) for more details. - -## API - -### Development - -You can access environment variables defined in `.env` and `.env.defaults` as `process.env.VAR_NAME`. For example, if we define the environment variable `HELLO_ENV` in `.env`: - -``` -HELLO_ENV=hello world -``` - -and make a hello Function (`yarn rw generate function hello`) and reference `HELLO_ENV` in the body of our response: - -```javascript{6} -// ./api/src/functions/hello.js - -export const handler = async (event, context) => { - return { - statusCode: 200, - body: `${process.env.HELLO_ENV}`, - } -} -``` - -Navigating to http://localhost:8911/hello shows that the Function successfully accesses the environment variable: - - - - -![rw-envVars-api](https://user-images.githubusercontent.com/32992335/86520528-47112100-bdfa-11ea-8d7e-1c0d502805b2.png) - -### Production - - - - -Whichever platform you deploy to, they'll have some specific way of making environment variables available to the serverless environment where your Functions run. For example, if you deploy to Netlify, you set your environment variables in **Settings** > **Build & Deploy** > **Environment**. You'll just have to read your provider's documentation. - -## Keeping Sensitive Information Safe - -Since it usually contains sensitive information, you should [never commit your `.env` file](https://github.com/motdotla/dotenv#should-i-commit-my-env-file). Note that you'd actually have to go out of your way to do this as, by default, a Redwood app's `.gitignore` explicitly ignores `.env`: - -```plaintext{2} -.DS_Store -.env -.netlify -dev.db -dist -dist-babel -node_modules -yarn-error.log -``` - -## Where Does Redwood Load My Environment Variables? - -For all the variables in your `.env` and `.env.defaults` files to make their way to `process.env`, there has to be a call to `dotenv`'s `config` function somewhere. So where is it? - -It's in [the CLI](https://github.com/redwoodjs/redwood/blob/main/packages/cli/src/index.js#L6-L12)—every time you run a `yarn rw` command: - -```javascript -// packages/cli/src/index.js - -import { config } from 'dotenv-defaults' - -config({ - path: path.join(getPaths().base, '.env'), - encoding: 'utf8', - defaults: path.join(getPaths().base, '.env.defaults'), -}) -``` - -Remember, if `yarn rw dev` is already running, your local app won't reflect any changes you make to your `.env` file until you stop and re-run `yarn rw dev`. diff --git a/docs/versioned_docs/version-1.0/forms.md b/docs/versioned_docs/version-1.0/forms.md deleted file mode 100644 index c74afb927a94..000000000000 --- a/docs/versioned_docs/version-1.0/forms.md +++ /dev/null @@ -1,403 +0,0 @@ -# Forms - -Redwood provides several helpers to make building forms easier. -All of Redwood's helpers are simple wrappers around [React Hook Form](https://react-hook-form.com/) (RHF) that make it even easier to use in most cases. - -If Redwood's helpers aren't flexible enough for you, you can use React Hook Form directly. `@redwoodjs/forms` exports everything it does: - -```javascript -import { - useForm, - useFormContext, - /** - * Or anything else React Hook Form exports! - * - * @see {@link https://react-hook-form.com/api} - */ -} from '@redwoodjs/forms' -``` - -## Overview - -`@redwoodjs/forms` exports the following components: - -| Component | Description | -|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------| -| `
` | Surrounds all components, providing form and error contexts | -| `` | Displays error messages from the server. Typically placed at the top of your form | -| `