Skip to main content

Quickstart: RedwoodJS

Introduction

This quickstart guide provides the steps you need to build a simple RedwoodJS app powered by Nhost for the backend. It includes:

By the end of this guide, you'll have a full-stack app that allows users to log in to access a protected dashboard and update their profile information.

Prerequisites

Before getting started, let's make sure that your development environment is ready.

To check, please run the following commands from your terminal:

node --version
yarn --version
caution

If your system versions do not meet both requirements, the RedwoodJS installation will result in an error. So, make sure you have the correct versions before moving on.

Project setup

Create a new Nhost app

First things first, we need to create a new Nhost project.

So, log in to your Nhost dashboard and click the Create Your First Project button.

Nhost Dashboard

Next, give your new Nhost project a name, select a geographic region for your Nhost services and click Create Project.

New Nhost project

After a few seconds, you should get a PostgreSQL database, a GraphQL API with Hasura, file storage, and authentication set up.

Finally, update your client login URL from your Nhost dashboard as the local RedwoodJS server starts on port 8910 instead of the usual 3000 port you may be used to with a regular React app.

nhost-first-app

info

You can also connect your Nhost project to a Git repository at GitHub. When you do this, any updates you push to your code will automatically be deployed. Learn more

Initialize the app

Create a RedwoodJS app

The simplest way to create a new RedwoodJS application is by using the yarn create command, which bootstraps a RedwoodJS app for you without the hassle of configuring everything yourself.

So, open your terminal, and run the following command:

yarn create redwood-app my-nhost-app

You can now cd into your project directory:

cd my-nhost-app

And run the development server with the following command:

yarn rw dev

If everything is working fine, your RedwoodJS development server should be running on port 8910. Open http://localhost:8910 from your browser to check this out.

RedwoodJS - Default homepage

Configure Nhost with RedwoodJS

To work with Nhost from within our RedwoodJS app, we'll use the JavaScript SDK provided by Nhost which allows us to interact with our Nhost backend using a standard interface.

You can install the Nhost JavaScript SDK with the following CLI command:

yarn rw setup auth nhost
caution

When prompted "Overwrite existing /api/src/lib/auth.[jt]s?", reply yes.

RedwoodJS - Configure Nhost

In addition to installing the Nhost JavaScript SDK, this command configures and instantiates Nhost as the authentication client in our RedwoodJS app. It makes the authentication state and all the provided hooks used with Nhost auth available in our application through the <AuthProvider>.

web/src/App.js
import { AuthProvider } from '@redwoodjs/auth'
import { NhostClient } from '@nhost/nhost-js'

const nhostClient = new NhostClient({
subdomain: process.env.NHOST_SUBDOMAIN
region: process.env.NHOST_REGION
})

const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
<AuthProvider client={nhostClient} type="nhost">
<RedwoodApolloProvider>
<Routes />
</RedwoodApolloProvider>
</AuthProvider>
</RedwoodProvider>
</FatalErrorBoundary>
)

export default App

Next, as we are also using Nhost as our GraphQL API server instead of the standard RedwoodJS GraphQL Server, we need to pass skipFetchCurrentUser as a prop to AuthProvider, as follows:

web/src/App.js
const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
<AuthProvider client={nhostClient} type="nhost" skipFetchCurrentUser>
<RedwoodApolloProvider>
<Routes />
</RedwoodApolloProvider>
</AuthProvider>
</RedwoodProvider>
</FatalErrorBoundary>
)

That prop avoids having an additional request to fetch the current user.

Next, store the environment variables for subdomain and region in .env:

.env
NHOST_SUBDOMAIN=[subdomain]
NHOST_REGION=[region]

You find your Nhost project's subdomain and region in the project overview:

Project Overview

Finally, we need to customize the GraphQL Endpoint for our RedwoodJS app. Indeed, by default, RedwoodJS provides a built-in GraphQL server under the api side. However, as we already have our own GraphQL server through Nhost and Hasura, we do not need it.

So, open the RedwoodJS configuration file, redwood.toml, and change the GraphQL endpoint via the apiGraphQLUrl option, as follows:

redwood.toml
[web]
apiUrl = "/.redwood/functions"
apiGraphQLUrl = "https://${NHOST_SUBDOMAIN}.graphql.${NHOST_REGION}.nhost.run/v1"
caution

Don't forget to restart your RedwoodJS server after saving your .env and redwood.toml files to load your new environment variable and configuration.

Setup a styling library

For the purpose of this guide, we'll set up Tailwind CSS as our styling library inside our project as the installation process is straightforward and supported by default by RedwoodJS.

Indeed, you only need to run the following command:

yarn rw setup ui tailwindcss

RedwoodJS will take care of installing the required dependencies, configuring PostCSS, initializing Tailwind CSS, and adding the necessary imports.

caution

You may need to restart your RedwoodJS server after setting up Tailwind CSS for your configuration to take effect.

Finally, add the following extra custom CSS classes to your index.css file so we can re-use them throughout our app to style some of the repeated HTML elements (input and button):

web/src/index.css
/**
* START --- SETUP TAILWINDCSS EDIT
*
* `yarn rw setup ui tailwindcss` placed these imports here
* to inject Tailwind's styles into your CSS.
* For more information, see: https://tailwindcss.com/docs/installation#include-tailwind-in-your-css
*/
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
/**
* END --- SETUP TAILWINDCSS EDIT
*/

@layer components {
.input {
@apply w-full shadow-sm rounded-md p-3 border border-gray-300 transition;
}
.input:focus {
@apply outline-none border-blue-500 ring-blue-500 ring-4 ring-opacity-20;
}
.input:disabled {
@apply opacity-50 cursor-not-allowed;
}
.input:read-only {
@apply opacity-50 cursor-not-allowed;
}
.input:read-only:focus {
@apply border-gray-300 focus:ring-0;
}

.label {
@apply text-gray-700 font-medium text-sm mb-1;
}

.btn-primary {
@apply w-full font-medium inline-flex justify-center items-center rounded-md p-3 text-white bg-blue-600 transition;
}
.btn-primary:enabled:hover {
@apply bg-blue-500;
}
.btn-primary:focus {
@apply outline-none ring-4 ring-blue-500 ring-opacity-50;
}
.btn-primary:disabled {
@apply opacity-50 cursor-not-allowed;
}

.btn-secondary {
@apply py-2 px-4 rounded-md bg-gray-700 text-white transition;
}
.btn-secondary:enabled:hover {
@apply bg-gray-600;
}
.btn-secondary:focus {
@apply outline-none ring-4 ring-gray-700 ring-opacity-20;
}
.btn-secondary:disabled {
@apply opacity-50 cursor-not-allowed;
}
}
info

Check the official documentation to see all the UI libraries supported by the setup ui command.

Build the app

Create the pages

Let's start by creating the pages of our project using the redwood CLI:

yarn rw generate page home /
yarn rw generate page profile /profile
yarn rw generate page SignUp /sign-up
yarn rw generate page SignIn /sign-in

Each command will create a new page component under the web/pages folder and add a <Route> in web/src/Routes.js that maps the path to the new page.

web/src/Routes.js
import { Router, Route } from '@redwoodjs/router'

const Routes = () => {
return (
<Router>
<Route path="/" page={HomePage} name="home" />
<Route path="/profile" page={ProfilePage} name="profile" />
<Route path="/sign-in" page={SignInPage} name="signIn" />
<Route path="/sign-up" page={SignUpPage} name="signUp" />
<Route notfound page={NotFoundPage} />
</Router>
)
}

export default Routes

Your pages should be up and running already with some default content. For example, if you visit your homepage at http://localhost:8910, here's what you should see:

RedwoodJS - New homepage

Create the layout

As both our HomePage and ProfilePage components will have the same layout, we can wrap them inside a single layout component that defines the shared UI and then renders the page as its child. That saves us from writing duplicated code.

So, let's create that layout by running the following command:

yarn redwood g layout dash

That command creates the layout component in the web/src/layouts/DashLayout/DashLayout.js file. So, open up this file and use the following code to define the UI of the layout:

web/src/layouts/DashLayout/DashLayout.js
import { Fragment } from 'react'
import { Link, navigate, routes } from '@redwoodjs/router'
import { Menu, Transition } from '@headlessui/react'
import { ChevronDownIcon, HomeIcon, LogoutIcon, UserIcon } from '@heroicons/react/outline'

const Avatar = ({ src = '', alt = '' }) => {
return (
<div className="rounded-full bg-gray-100 overflow-hidden w-9 h-9">
{src ? <img src={src} alt={alt} /> : null}
</div>
)
}

const DashLayout = ({ children }) => {
const user = null

const menuItems = [
{
label: 'Dashboard',
onClick: () => navigate(routes.home()),
icon: HomeIcon
},
{
label: 'Profile',
onClick: () => navigate(routes.profile()),
icon: UserIcon
},
{
label: 'Logout',
onClick: () => null,
icon: LogoutIcon
}
]

return (
<div>
<header className="fixed z-10 top-0 inset-x-0 h-[60px] shadow bg-white">
<div className="container mx-auto px-4 py-3 flex justify-between">
<Link to={routes.home()} className="h-9">
<img src="https://i.imgur.com/CmLlufz.png" alt="logo" className="max-h-full w-auto" />
</Link>

<Menu as="div" className="relative z-50">
<Menu.Button className="flex items-center space-x-px group">
<Avatar src={user?.avatarUrl} alt={user?.displayName} />
<ChevronDownIcon className="w-5 h-5 shrink-0 text-gray-500 group-hover:text-current" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 w-72 overflow-hidden mt-1 divide-y divide-gray-100 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="flex items-center space-x-2 py-4 px-4 mb-2">
<div className="shrink-0">
<Avatar src={user?.avatarUrl} alt={user?.displayName} />
</div>
<div className="flex flex-col truncate">
<span>{user?.displayName}</span>
<span className="text-sm text-gray-500">{user?.email}</span>
</div>
</div>

<div className="py-2">
{menuItems.map(({ label, onClick, icon: Icon }) => (
<div key={label} className="px-2 last:border-t last:pt-2 last:mt-2">
<Menu.Item>
<button
onClick={onClick}
className="w-full flex items-center space-x-2 py-2 px-4 rounded-md hover:bg-gray-100"
>
<Icon className="w-5 h-5 shrink-0 text-gray-500" />
<span>{label}</span>
</button>
</Menu.Item>
</div>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</header>

<main className="mt-[60px]">
<div className="container mx-auto px-4 py-12">{children}</div>
</main>
</div>
)
}

export default DashLayout

Make sure to install the following third-party dependencies as we are using them to create the dropdown menu and render SVG icons:

yarn workspace web add @headlessui/react @heroicons/react

Finally, we need to edit the Routes.js file to render our layout.

So, import the DashLayout component at the top and wrap the / and /profile routes inside it using the Set component from @redwoodjs/router:

web/src/Routes.js
import { Router, Route, Set } from '@redwoodjs/router'
import DashLayout from 'src/layouts/DashLayout'

const Routes = () => {
return (
<Router>
<Set wrap={DashLayout}>
<Route path="/" page={HomePage} name="home" />
<Route path="/profile" page={ProfilePage} name="profile" />
</Set>
<Route path="/sign-in" page={SignInPage} name="signIn" />
<Route path="/sign-up" page={SignUpPage} name="signUp" />
<Route notfound page={NotFoundPage} />
</Router>
)
}

Your home and profile pages should now look something like this:

RedwoodJS - App layout

Add authentication

1. Sign-up

The next step is to allow our users to authenticate into our application. Let's start with implementing the sign-up process.

For that, we'll use the useAuth hook provided by RedwoodJS (which leverages the Nhost JavaScript SDK under the hood) within our sign-up page.

So, open up the corresponding file from your project, and use the following code:

web/src/pages/SignUpPage/SignUpPage.js
import { MetaTags } from '@redwoodjs/web'
import { useState } from 'react'
import { Link, Redirect, routes, useParams } from '@redwoodjs/router'
import { useAuth } from '@redwoodjs/auth'
import { Form, Label, EmailField, PasswordField, TextField, Submit } from '@redwoodjs/forms'

const SignUpPage = () => {
const { isAuthenticated, signUp } = useAuth()
const { redirectTo } = useParams()

const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [needsEmailVerification, setNeedsEmailVerification] = useState(false)

const handleOnSubmit = async ({ email, password, firstName, lastName }) => {
try {
setLoading(true)
const { session, error } = await signUp({
email,
password,
options: {
displayName: `${firstName} ${lastName}`.trim(),
metadata: {
firstName,
lastName
}
}
})
if (error) {
throw error
}
if (!session) {
setNeedsEmailVerification(true)
setLoading(false)
}
} catch (error) {
setError(error.message)
setLoading(false)
}
}

if (isAuthenticated) {
return <Redirect to={redirectTo ?? routes.home()} options={{ replace: true }} />
}

return (
<>
<MetaTags title="Sign Up" description="SignUp page" />

<div className="h-screen flex items-center justify-center py-6">
<div className="w-full max-w-lg">
<div className="sm:rounded-xl sm:shadow-md sm:border border-opacity-50 sm:bg-white px-4 sm:px-8 py-12 flex flex-col items-center">
<div className="h-14">
<img src="https://i.imgur.com/CmLlufz.png" alt="logo" className="w-full h-full" />
</div>

{needsEmailVerification ? (
<p className="mt-12 text-center">
Please check your mailbox and follow the verification link to verify your email.
</p>
) : (
<Form onSubmit={handleOnSubmit} className="w-full">
<div className="mt-12 flex flex-col items-center space-y-6">
<div className="w-full flex gap-6">
<div className="w-full flex flex-col">
<Label name="firstName" className="label">
First name
</Label>
<TextField name="firstName" required className="input" disabled={loading} />
</div>
<div className="w-full flex flex-col">
<Label name="lastName" className="label">
Last name
</Label>
<TextField name="lastName" required className="input" disabled={loading} />
</div>
</div>
<div className="w-full flex flex-col">
<Label name="email" className="label">
Email address
</Label>
<EmailField name="email" required className="input" disabled={loading} />
</div>
<div className="w-full flex flex-col">
<Label name="password" className="label">
Password
</Label>
<PasswordField name="password" required className="input" disabled={loading} />
</div>
</div>

<Submit className="mt-6 btn-primary" disabled={loading}>
Create account
</Submit>

{error ? <p className="mt-4 text-red-500 text-center">{error}</p> : null}
</Form>
)}
</div>

{!needsEmailVerification ? (
<p className="sm:mt-8 text-gray-500 text-center">
Already have an account?{' '}
<Link
to={routes.signIn()}
className="text-blue-600 hover:text-blue-500 hover:underline hover:underline-offset-1 transition"
>
Sign in
</Link>
</p>
) : null}
</div>
</div>
</>
)
}

export default SignUpPage

By default, the user must verify his email address before fully signing up. You can change this setting from your Nhost dashboard.

RedwoodJS - Sign-up page

2. Sign-in

Now that new users can sign up for our application let's see how to allow existing users to sign in with email and password.

For that, we will also use the useAuth hook from RedwoodJS inside our sign-in page the same way we did with our sign-up page. So, here's what your component should look like after applying the changes for the sign-in logic:

web/src/pages/SignInPage/SignInPage.js
import { MetaTags } from '@redwoodjs/web'
import { useState } from 'react'
import { Link, Redirect, routes, useParams } from '@redwoodjs/router'
import { useAuth } from '@redwoodjs/auth'
import { Form, Label, EmailField, PasswordField, Submit } from '@redwoodjs/forms'

const SignInPage = () => {
const { isAuthenticated, logIn } = useAuth()
const { redirectTo } = useParams()

const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [needsEmailVerification, setNeedsEmailVerification] = useState(false)

const handleOnSubmit = async ({ email, password }) => {
try {
setLoading(true)
const { error } = await logIn({
email,
password
})
if (error) {
if (error?.error === 'unverified-user') {
setNeedsEmailVerification(true)
setLoading(false)
} else {
throw error
}
}
} catch (error) {
setError(error.message)
setLoading(false)
}
}

if (isAuthenticated) {
return <Redirect to={redirectTo ?? routes.home()} options={{ replace: true }} />
}

return (
<>
<MetaTags title="Sign In" description="SignIn page" />

<div className="h-screen flex items-center justify-center py-6">
<div className="w-full max-w-lg">
<div className="sm:rounded-xl sm:shadow-md sm:border border-opacity-50 sm:bg-white px-4 sm:px-8 py-12 flex flex-col items-center">
<div className="h-14">
<img src="https://i.imgur.com/CmLlufz.png" alt="logo" className="w-full h-full" />
</div>

{needsEmailVerification ? (
<p className="mt-12 text-center">
Please check your mailbox and follow the verification link to verify your email.
</p>
) : (
<Form onSubmit={handleOnSubmit} className="w-full">
<div className="mt-12 w-full flex flex-col items-center space-y-6">
<div className="w-full flex flex-col">
<Label name="email" className="label">
Email address
</Label>
<EmailField name="email" required className="input" disabled={loading} />
</div>

<div className="w-full flex flex-col">
<Label name="password" className="label">
Password
</Label>
<PasswordField name="password" required className="input" disabled={loading} />
</div>
</div>

<Submit className="mt-6 btn-primary" disabled={loading}>
Sign in
</Submit>

{error ? <p className="mt-4 text-red-500 text-center">{error}</p> : null}
</Form>
)}
</div>

{!needsEmailVerification ? (
<p className="sm:mt-8 text-gray-500 text-center">
No account yet?{' '}
<Link
to={routes.signUp()}
className="text-blue-600 hover:text-blue-500 hover:underline hover:underline-offset-1 transition"
>
Sign up
</Link>
</p>
) : null}
</div>
</div>
</>
)
}

export default SignInPage

RedwoodJS - Sign-in page

3. Sign-out

Finally, to allow the users to sign out from the app, we can use the RedwoodJS logOut method from the useAuth hook:

web/src/layouts/DashLayout/DashLayout.js
import { useAuth } from '@redwoodjs/auth'

const DashLayout = ({ children }) => {
const { logOut } = useAuth()

const menuItems = [
//...
{
label: 'Logout',
onClick: logOut,
icon: LogoutIcon
}
]

//...
}

Protect routes

Now that we have implemented authentication, we can easily decide who can access certain parts of our application.

In our case, we'll only allow authenticated users to have access to the / and /profile routes. All the other users should be redirected to the /sign-in page if they try to access those routes.

To do so, we can wrap those two routes inside the Private component provided by the built-in RedwoodJS router:

web/src/Routes.js
import { Private, Router, Route, Set } from '@redwoodjs/router'
import DashLayout from './layouts/DashLayout/DashLayout'

const Routes = () => {
return (
<Router>
<Private unauthenticated="signIn">
<Set wrap={DashLayout}>
<Route path="/" page={HomePage} name="home" />
<Route path="/profile" page={ProfilePage} name="profile" />
</Set>
</Private>
<Route path="/sign-up" page={SignUpPage} name="signUp" />
<Route path="/sign-in" page={SignInPage} name="signIn" />
<Route notfound page={NotFoundPage} />
</Router>
)
}

Retrieve user data

Finally, let's display the information of the authenticated user throughout his dashboard to make the app more personalized.

Getting the current authenticated user data is quite easy. Indeed, we can use the userMetadata variable returned by the useAuth hook.

So, open the files where we need the user's data and retrieve it like so:

web/src/layouts/DashLayout/DashLayout.js
const DashLayout = ({ children }) => {
const { logOut, userMetadata: user } = useAuth()
//...
}

RedwoodJS - Homepage with data

Update user data

Nhost provides a GraphQL API through Hasura so that we can query and mutate our data instantly.

Also, RedwoodJS comes with the Apollo Client already set up and ready to query thanks to the RedwoodApolloProvider, which wraps the ApolloProvider. Hence, we don't need to install or configure any GraphQL client ourselves.

web/src/App.js
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
// ...

const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
<AuthProvider client={nhostClient} type="nhost" skipFetchCurrentUser>
<RedwoodApolloProvider>
<Routes />
</RedwoodApolloProvider>
</AuthProvider>
</RedwoodProvider>
</FatalErrorBoundary>
)

From there, we can construct our GraphQL query and use the Apollo useMutation hook to execute that query when the user submits the form from the profile page:

web/src/pages/ProfilePage/ProfilePage.js
import { Form, Label, Submit, EmailField, TextField } from '@redwoodjs/forms'
import { MetaTags, useMutation } from '@redwoodjs/web'
import { useAuth } from '@redwoodjs/auth'

const UPDATE_USER_MUTATION = gql`
mutation ($id: uuid!, $displayName: String!, $metadata: jsonb) {
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName, metadata: $metadata }) {
id
displayName
metadata
}
}
`

const ProfilePage = () => {
const { userMetadata: user } = useAuth()

const [mutateUser, { loading }] = useMutation(UPDATE_USER_MUTATION)

const updateUserProfile = async ({ firstName, lastName }) => {
try {
await mutateUser({
variables: {
id: user.id,
displayName: `${firstName} ${lastName}`.trim(),
metadata: {
firstName,
lastName
}
}
})
alert('Updated successfully')
} catch (error) {
alert('Unable to update profile')
}
}

//...
}

Finally, since Hasura has an allow nothing by default policy, and we haven't set any permissions yet, our GraphQL mutations would fail.

So, open the Hasura console from the Data tab of your project from your Nhost dashboard. Then, go to the permissions tab of the users table, type in user in the role cell, and click the edit icon on the select operation:

Hasura users permissions

To restrict the user to read his own data only, specify a condition with the user's ID and the X-Hasura-User-ID session variable, which is passed with each requests.

Hasura users permissions

Next, select the columns you'd like the users to have access to, and click Save Permissions.

Hasura users permissions

Repeat the same steps on the update operation for the user role to allow users to update their displayName and metadata only.

Finally, to add caching, synchronizing, and updating server state in your RedwoodJS app, let's refactor the user data fetching using the Apollo client and our GraphQL API instead.

So, first, add the following GraphQL query to retrieve the current user data, and use React Context to run that query and share its result throughout your application:

web/src/UserProvider.js
import React, { useContext } from 'react'
import { useAuth } from '@redwoodjs/auth'
import { useQuery } from '@redwoodjs/web'

const GET_USER_QUERY = gql`
query GetUser($id: uuid!) {
user(id: $id) {
id
email
displayName
metadata
avatarUrl
}
}
`

const UserContext = React.createContext(null)

export function UserProvider({ children = null }) {
const { userMetadata } = useAuth()

const { loading, error, data } = useQuery(GET_USER_QUERY, {
variables: { id: userMetadata?.id },
skip: !userMetadata?.id
})
const user = data?.user

if (error) {
return <p>Something went wrong. Try to refresh the page.</p>
}
if (loading) {
return null
}

return <UserContext.Provider value={{ user }}>{children}</UserContext.Provider>
}

export function useUserContext() {
return useContext(UserContext)
}

Then, add the UserProvider inside your Routes.js file as follow:

web/src/Routes.js
import { UserProvider } from './UserProvider'

const Routes = () => {
return (
<Router>
<Private unauthenticated="signIn">
<Set wrap={[UserProvider, DashLayout]}>
<Route path="/" page={HomePage} name="home" />
<Route path="/profile" page={ProfilePage} name="profile" />
</Set>
</Private>
<Route path="/sign-up" page={SignUpPage} name="signUp" />
<Route path="/sign-in" page={SignInPage} name="signIn" />
<Route notfound page={NotFoundPage} />
</Router>
)
}

Finally, replace the userMetadata with the useUserContext hook to retrieve the current user data inside the following files:

web/src/layouts/DashLayout/DashLayout.js
import { useUserContext } from 'src/UserProvider'

const DashLayout = ({ children }) => {
const { user } = useUserContext()
const { logOut } = useAuth()
//...
}

You now have a fully functional RedwoodJS application powered by Nhost on the backend. Congratulations!

Next Steps

  • Did you enjoy Nhost? Give us a star ⭐ on Github. Thank you!
  • Check out our more in-depth examples.
  • Build your next app with Nhost!