This is the upgrade guide for the v0.38 Release Notes . Read that first (if you havenât already).
New Automated Codemods
Many upgrade steps can be completed using the @redwoodjs/codemods
package (preview feature). Amazing! Codemods are applied using the npm
subcommand npx
, which runs a package without needing to install either locally or globally. Hereâs an example codemod youâll run from your project root directory:
Codemod Available
npx @redwoodjs/codemods update-scaffold-styles
There are a few other things you should know in order to get the most out of this preview feature:
- Start by saving the current state of your project with git â there should be no uncommitted changes. This way, if you ever need to re-try or rollback a codemod step, you can easily revert using git.
- Always review the file changes after you run a codemod. Theyâre going to be accurate 90% of the time. The remaining 10% is on you.
- Heads up Although weâve verified these Codemods with npm v7, previous Codemods only worked with npm v6, which is included with Node.js v14. (
npx
is a subcommand of thenpm
command.) If you encounter errors, they are likely because you are using npm v7 (Node.js v16). Confirm either way by runningnpm --version
. (Need to switch versions? Try nvm for Mac or Windows.)
1. Improved Scaffold Styles
Scaffold inputs now have a soft red glow when focused with errors. (See PR#3591)
Codemod Available
To implement this step via automated codemod, run:
npx @redwoodjs/codemods update-scaffold-styles
Click for manual steps
Just add the following to your web/src/scaffold.css
:
.rw-input-error:focus {
outline: none;
border-color: #c53030;
box-shadow: 0 0 5px #c53030;
}
2. Node.js v16
Redwood now supports Node.js v16 âGalliumâ, the new LTS version.
Codemod Available
To implement this step via automated codemod, run:
npx @redwoodjs/codemods update-node-engine
Click for manual steps
Make the following change to the âenginesâ property in your root ./package.json
:
"engines": {
- "node": ">=14.x",
+ "node": ">=14.x <=16.x",
"yarn": ">=1.15"
},
3. redwood.toml
: remove apiProxyPath
and replace it with apiUrl
Weâve greatly improved Redwoodâs api-path configuration in this release. While the update you need to make now is simple, you should also take a look at the other new options and configuration examples here in the Docs.
Codemod Available
To implement this step via automated codemod, run:
npx @redwoodjs/codemods rename-api-proxy-path
Using
apiProxyPath
elsewhere?This codemod only renames apiProxyPath in
redwood.toml
.
Click for manual steps
Make the following change in your redwood.toml
:
[web]
- apiProxyPath = "/.redwood/functions" # value may be different
+ apiUrl = "/.redwood/functions" # value may be different
Other Changes
If you use apiProxyPath
or __REDWOOD__API_PROXY_PATH
anywhere else in your project, youâll need to update it there as well.
Just âfindâ â âreplaceâ:
apiProxyPath
âapiUrl
__REDWOOD__API_PROXY_PATH
âRWJS_API_GRAPHQL_URL
4. Migrate to the new Prisma db seed
With Prisma v3, we made some changes to the way you seed your database. Upgrading your Redwood project is a two-step process.
Step 1. Add the âprismaâ property to package.json and create scripts/seed.js
Codemod Available
To implement this step via automated codemod, run:
npx @redwoodjs/codemods update-seed-script
Have an existing seed file?
Note that this codemod doesnât migrate your existing seed file overâit just adds the template to the
scripts
directory.Once youâve copied over the logic, you can delete the seed file in
api/db
.
Click for manual steps
You'll have to add the `"prisma"` property to your project's root `package.json`:+ "prisma": {
+ "seed": "yarn rw exec seed"
+ }
This tells Prisma how to seed your database, using the seed file in the scripts
directory.
If you donât have a scripts
directory at the root of your project (alongside api
and web
), make one now and a file named seed
with the appropriate .js
or .ts
extension:
$ mkdir scripts
$ touch scripts/seed.js
Then copy the template for the scripts/seed.{js,ts}
file from here and paste its contents into the one you just made. If your project is JS instead of TS, youâll have to remove the types (just make the red squiggles go away).
Step 2. Move over logic from api/db/seed.{js,ts}
to scripts/seed.{js,ts}
If you use the existing seed file in api/db
, youâll have to move the logic over to the file named seed.{js,ts}
in the scripts
directory. This should be relatively straightforward â not much has changed between the files aside from the way the db is imported and instantiated. Simply follow the new template in scripts/seed.{js,tx}
, replacing the data and models with your existing Seed file information.
Once youâre done, or if you are not using the Seed file, you can delete the old seed file in api/db/seed.{js,ts}
.
Be sure to read the updated docs.
Prisma v3 Upgrade Guide
For the most part, Redwood v0.38 internally handles the Prisma v3 upgrade. However, mileage will vary based on your projectâs specific implementation. You should refer to Prismaâs v3 upgrade guide:
5. dbAuth: add Forgot/Reset Password
This is a required upgrade for projects using dbAuth.
If you have been using dbAuth since pre-0.37.0 there are a couple of steps to get Forgot/Reset Password added to your app.
For a full overview, see the introduction to PR#3429
Click here for code modification instructions
If you donât mind overriding your existing Login and Signup pages then running the generator again will be the quickest way to get up to speed:
yarn rw g dbAuth --force
If youâve customize those pages, or moved them, then youâll want to follow the instructions in the Generate Pages section below.
In either case, youâll still need to make the schema changes and auth.js
function changes, however.
Schema Updates
Add two new columns to your User
schema (whatever you named it):
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
hashedPassword String
salt String
resetToken String? // <--- new
resetTokenExpiresAt DateTime? // <--- new
}
In api/src/functions/auth.js
youâll need to let Redwood know what you named these columns:
authFields: {
id: 'id',
username: 'email',
hashedPassword: 'hashedPassword',
salt: 'salt',
resetToken: 'resetToken', // <--- new
resetTokenExpiresAt: 'resetTokenExpiresAt', // <--- new
},
functions/auth.js Updates
In addition to the update to the authFields
object above, youâll need a new key for forgotPassword
and another for resetPassword
options:
forgotPassword: {
// Invoked after verifying that a user was found with the given username.
// This is where you can send the user an email with a link to reset their
// password. With the default dbAuth routes and field names, the URL
// to reset the password will be:
//
// https://example.com/reset-password?resetToken=${user.resetToken}
//
// Whatever is returned from this function will be returned from
// the `forgotPassword()` function that is destructured from `useAuth()`
// You could use this return value to, for example, show the email
// address in a toast message so the user will know it worked and where
// to look for the email.
handler: (user) => {
return user
},
// How long the resetToken is valid for, in seconds (default is 24 hours)
expires: 60 * 60 * 24,
errors: {
// for security reasons you may want to be vague here rather than expose
// the fact that the email address wasn't found (prevents fishing for
// valid email addresses)
usernameNotFound:
'If a user with that username exists you will receive an email with a link to reset your password',
// if the user somehow gets around client validation
usernameRequired: 'Gotta have a username yo',
},
},
resetPassword: {
// Invoked after the password has been successfully updated in the database.
//
// Return the user to automatically log them in
// Return `false` and the user will NOT be logged in automatically. You
// can then, for example, redirect them to the Login page
handler: (user) => {
return user
},
// If `false` then the new password MUST be different than the current one
allowReusedPassword: false,
errors: {
// the resetToken is valid, but expired
resetTokenExpired: 'This reset token is old, try again',
// no user was found with the given resetToken
resetTokenInvalid: 'This reset token was not found',
// the resetToken was not present in the URL
resetTokenRequired: 'You have to include a reset token',
// new password is the same as the old password (apparently they did not forget it)
reusedPassword: 'You must choose a new password',
},
},
With comments removed, the whole file now looks something like:
import { db } from 'src/lib/db'
import { DbAuthHandler } from '@redwoodjs/api'
export const handler = async (event, context) => {
const forgotPasswordOptions = {
handler: (user) => {
return user
},
expires: 60 * 60 * 24,
errors: {
usernameNotFound: 'Username not found',
usernameRequired: 'Username is required',
},
}
const loginOptions = {
handler: (user) => {
return user
},
errors: {
usernameOrPasswordMissing: 'Both username and password are required',
usernameNotFound: 'Username ${username} not found',
incorrectPassword: 'Incorrect password for ${username}',
},
expires: 60 * 60 * 24 * 365 * 10,
}
const resetPasswordOptions = {
handler: (user) => {
return true
},
allowReusedPassword: true,
errors: {
resetTokenExpired: 'resetToken is expired',
resetTokenInvalid: 'resetToken is invalid',
resetTokenRequired: 'resetToken is required',
reusedPassword: 'Must choose a new password',
},
}
const signupOptions = {
handler: ({ username, hashedPassword, salt, userAttributes }) => {
return db.user.create({
data: {
email: username,
hashedPassword: hashedPassword,
salt: salt,
// name: userAttributes.name
},
})
},
errors: {
fieldMissing: '${field} is required',
usernameTaken: 'Username `${username}` already in use',
},
}
const authHandler = new DbAuthHandler(event, context, {
db: db,
authModelAccessor: 'user',
authFields: {
id: 'id',
username: 'email',
hashedPassword: 'hashedPassword',
salt: 'salt',
resetToken: 'resetToken',
resetTokenExpiresAt: 'resetTokenExpiresAt',
},
forgotPassword: forgotPasswordOptions,
login: loginOptions,
resetPassword: resetPasswordOptions,
signup: signupOptions,
})
return await authHandler.invoke()
}
Generate Pages
If you do not want to overwrite your existing Login and Signup pages, you can generate just the Forgot Password and Reset Password pages by adding a couple of flags to the generate command:
yarn rw generate dbAuth --skip-login --skip-signup
Youâll want to a link to the Forgot Password page in your Login page:
<Link to={routes.forgotPassword()}>Forgot Password?</Link>
Add Styles
Youâll need to add a couple of new styles to your web/src/scaffold.css
file (assuming you are still using it for styling, if not then nevermind!):
.rw-toast {
background-color: #fff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
.rw-forgot-link {
font-size: 0.75rem;
color: #a0aec0;
text-align: right;
margin-top: 0.1rem;
}
.rw-forgot-link:hover {
font-size: 0.75rem;
color: #4299e1;
}
6. Magic.link: API token validation and decoding
Applies to projects using Magic.link Auth.
Other than adding a new API-side dependency, this is otherwise backwards compatible for existing projects that use magic link for authentication.
For a full overview, see the introduction to PR#3432
Click here for details and upgrade instructions
This change populates API-side requests context with the decoded magic link token. Only cryptographically validated and non-expired tokens are successfully decoded, otherwise an error is thrown and logged. See documentation describing the token format here: Getting Started with Decentralized ID (DID) Tokens | Magic
To update your project:
- Add the dependency
yarn workspace api add @magic-sdk/admin
- (Optionally) Update your api-side handlers or functions to use the decoded token. For example you might update your getCurrentUser() in api/src/lib/auth.[js,ts] file to make use of the first argument containing the decoded token, to see an example, please refer to the template that will populate api/src/lib/auth.[js,ts] for new projects: https://github.com/redwoodjs/redwood/blob/main/packages/cli/src/commands/setup/auth/templates/magicLink.auth.js.template
7. Remove @webpack-cli/serve
resolution in ./package.json
Projects using Redwood v0.37.3 (or v0.37.4) included a workaround in ./package.json
that resolved an upstream bug from the webpack-cli
dependency. As of v0.37.5, Projects no longer needed to âpinâ the dependency @webpack-cli/serve
to 1.5.2
.
Click here for details and upgrade instructions
If your project has the following resolution, the following change must be made in ./package.json
:
// ./package.json
{
"private": true,
"workspaces": {
"packages": [
"api",
"web",
"packages/*"
]
},
"devDependencies": {
"@redwoodjs/core": "0.37.3"
},
"eslintConfig": {
"extends": "@redwoodjs/eslint-config",
"root": true
},
"engines": {
"node": "14.x",
"yarn": "1.x"
+ }
- },
- "resolutions": {
- "@webpack-cli/serve": "1.5.2"
- }
}
Youâre almost done
Donât forget to upgrade your packages with yarn rw upgrade
!
Head back over to the Release Notes if you need specific instructions: