The Jamstack Goes Full-Stack: RedwoodJS Framework Tutorial

In a rush? Skip to the step-by-step tutorial or live demo

I can hear it already, screams through my screen, coming from heated dev-focused threads all over the web:

“Another JavaScript framework? Really?”

But trust us, we wouldn’t be talking about it if we didn’t think it was innovative and potentially special.

Time to meet RedwoodJS: a new Jamstack framework for full-stack web applications.

Don’t worry; I’ll clear up these concepts to understand what Redwood is all about. I’ll then follow with a step-by-step tutorial and live demo to see it in action.

Here’s what the tutorial covers:

  • Creating a RedwoodJS app
  • Generating the scaffolding for CRUD operations
  • Deploying the Redwood project
  • Securing the app with Netlify Identify & Redwood auth
  • Securing a GraphQL endpoint

Let’s get into it!

What is RedwoodJS?

redwoodjs-jamstack-framework

Redwood is the brainchild of great dev minds, including Tom Preston-Werner, whom you might know best as the co-founder of GitHub and Jekyll creator.

In the words of its official GitHub repo: “Redwood is an opinionated, full-stack, serverless web application framework that will allow you to build and deploy JAMstack applications with ease.”

Lot of concepts crammed into a single sentence. Let’s deconstruct this a little bit.

It’s important to note that Redwood is still in the alpha phase of development. While it's not recommended to go live with Redwood applications yet, all early input are useful.. Head to the Redwood community forum if you want to help!

Highly opinionated—Getting started with the Jamstack can be overwhelming because there are so many tools to choose from. It can take a while before assembling the necessary pieces for a new project, from static site generators to serverless functions and third-party APIs.

Redwood cuts to the chase in terms of decision-making by picking these pieces for you. With technologies such as React, GraphQL, and AWS Lambda, you’re in good hands, though.

Brings full-stack to the Jamstack—Redwood generates applications that have the necessary configurations to let your frontend talk to a database and all the parts in between.

Leverages serverless functions—They're front and center in Redwood’s philosophy. It opens up many opportunities for devs when it comes to scaling up projects without worrying about infrastructure maintenance. You can read our take on serverless functions right here.

Built on React—For developers with a strong React background (like me) learning Redwood isn’t such a stretch. Same thing for technologies such as GraphQL, Prisma, or AWS Lambda.

Need a hand getting started with these techs? I suggest these intros to React, GraphQL, Prisma, or AWS Lambda.

Why another framework for the Jamstack ecosystem?

Tools like React, which we’re used to calling “frameworks”, often act more as libraries. Redwood is a proper framework, deciding which technologies you should use, how to organize your code, how to name things, etc.

One of the issues many developers have with the Jamstack, and the JS ecosystem at large, is the lack of conventions.

In Redwood's own words: “We believes that there is power in standards”. In some ways, Redwood brings the philosophy of a framework like Rails to JavaScript. I particularly like the comparison Netlify paints here:

jamstack-full-stack-framework

As I already mentioned, it makes these decisions not only for your frontend but also for the backend. It results in full-stack applications that use JavaScript on both sides.

The following tutorial should help you understand better the concepts behind Redwood, and the stage of development the tool is at right now. I’ll build a simple CRUD CMS that could be used to create products for an e-commerce shop.

Let’s dive right in!

I strongly suggest this tutorial from Richard Haines, if you want to see a straight-up Snipcart integration with Redwood.

Redwood tutorial: trying out the full-stack Jamstack framework

redwoodjs-tutorial

Prerequisites

1. Creating a new RedwoodJS application

First things first, let’s create a new RedwoodJS project:

yarn create redwood-app redwoodjs-cms

2. Generating the product table

2.1 Adding a schema to Prisma

Prisma is an open-source database toolkit. It makes database access easy and is one of the selected tools that are part of the Redwood framework.

Open schema.prisma and change the UserExample model to the following:

model Product {
         id          Int     @default(autoincrement()) @id
         name        String
         price       Float
         description String?
         image       String?
}

2.2 Creating your first migration

Run the following command:

yarn rw db save

The redwood-cli now asks you if you want to create the database. Select “yes”. This action creates an SQLite database for development purposes.

It now asks for the migration name. In my case, I’ll go with createProductsTable

2.3 Running your migration on your local database

You can now set up migrations by running:

yarn rw db up

This command creates the product table in your database.

3. Building scaffolding for products CRUD

yarn rw g scaffold product

This command automatically scaffolded a few elements:

  • GraphQL queries and mutations
  • Service for Prisma to create, read, update and delete from your database
  • Pages necessary for your CRUD operations

Nice 👌

4. Updating the scaffolding

One downside to RedwoodJS is that the created form doesn't support floats by default, so we have to update it.

In ProductForm.js replace the onSubmit to the following:

const onSubmit = (data) => {
   data.price = parseFloat(data.price)
   props.onSave(data, props?.product?.id)
 }

Also, make sure the type of the price TextField is set to number.

<TextField
       name="price"
       type="number"
       defaultValue={props.product?.price}
       className={CSS.input}
       errorClassName={CSS.inputError}
       validation={{ required: true }}
       />

This is needed because by default “price” will be a string and it will later cause a problem when trying to add a new product.

5. Creating a new product

To add a product, you first have to run your project locally.

The command yarn rw dev creates a server.

Now, navigate to localhost:8910/products. From there, you can run your CRUD operations.

Go on and create a new product.

redwoodjs-app

Voilà! Your simple CRUD CMS should now be working effortlessly.

6. Deploying the CMS

Deploying your Redwood project requires a few things:

  1. Create a Netlify account (used to deploy our project)
  2. Create a Heroku account (used to host our database)

Then, I invite you to follow the deployment steps that are very well described in the RedwoodJS documentation.

7. Securing the CMS

Next, we want to secure the CMS and the API to make sure only allowed users to have access to your CRUD operations. I’ll use some functions offered by Netlify and Redwood itself to accomplish this.

7.1 Adding Netlify Identity

Head to Netlify Identity to add it.

Then, update the “Registration preferences” option to “Invite only”:

netlify-identify-settings

You can then add the netlify-identity-widget package to your web project using this command:

yarn workspace web add netlify-identity-widget

Go on and also add the redwoodjs/auth package to our web project:

yarn workspace web add @redwoodjs/auth

As of Redwood 0.7.0, you can simply run yarn rw g auth netlify instead of manually installing the packages and modifying several of the files. Head right here for more on that.

7.2 Integrating Netlify Identity and RedwoodJS auth

Create a new Homepage using the following command:

yarn rw g page Home /

It creates a new page called home on the / route.

Modify its content to:

import { useEffect } from "react";
import { navigate, routes } from '@redwoodjs/router'
import {useAuth} from '@redwoodjs/auth'

const HomePage = () => {
  const { loading, isAuthenticated, logIn } = useAuth()

  useEffect(() => {
    if(!loading && isAuthenticated){
      navigate(routes.products())
    }
  }, [loading, authenticated])

  if (loading) {
    return <div></div>
  }

  return (
    <div>
      <h1>HomePage</h1>
      <p>Find me in ./web/src/pages/HomePage/HomePage.js</p>
      <button onClick={logIn}>Login</button>
    </div>
  )
}

export default HomePage

Modify ProductsLayout.js to:

import { Link, routes, navigate } from '@redwoodjs/router'
import {useAuth} from '@redwoodjs/auth'

const ProductsLayout = (props) => {
  const {logOut} = useAuth()

  return (
    <div className="rw-scaffold">
      <button onClick={logOut}>Logout</button>
      <div className="bg-white font-sans">
        <header className="flex justify-between py-4 px-8">
          <h1 className="text-xl font-semibold">
            <Link
              to={routes.products()}
              className="text-gray-700 hover:text-gray-900 hover:underline"
            >
              Products
            </Link>
          </h1>
          <Link
            to={routes.newProduct()}
            className="flex bg-green-500 hover:bg-green-600 text-white text-xs font-semibold px-3 py-1 uppercase tracking-wide rounded"
          >
            <div className="text-xl leading-none">+</div>
            <div className="ml-1 leading-loose">New Product</div>
          </Link>
        </header>
        <main className="mx-4 pb-4">{props.children}</main>
      </div>
    </div>
  )
}

export default ProductsLayout

Now we need to add RedwoodJS Provider to add our Netlify token to the GraphQL requests.

Edit index.js with the following:

import ReactDOM from 'react-dom'
import { RedwoodProvider, FatalErrorBoundary } from '@redwoodjs/web'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import netlifyIdentity from 'netlify-identity-widget'
import { AuthProvider } from '@redwoodjs/auth'


netlifyIdentity.init()

import Routes from 'src/Routes'

import './scaffold.css'
import './index.css'

ReactDOM.render(
  <FatalErrorBoundary page={FatalErrorPage}>
     <AuthProvider client={netlifyIdentity} type="netlify">
       <RedwoodProvider>
         <Routes/>
         </RedwoodProvider>
    </AuthProvider>
  </FatalErrorBoundary>,
  document.getElementById('redwood-app')
)

Finally, we want to protect our products’ routes. In routes.js, add the following code:

import { Router, Route, Private } from '@redwoodjs/router'

const Routes = () => {
  return (
    <Router>
      <Route path="/" page={HomePage} name="home" />
      <Private unauthenticated="home">
        <Route path="/products/new" page={NewProductPage} name="newProduct" />
        <Route path="/products/{id:Int}/edit" page={EditProductPage} name="editProduct" />
        <Route path="/products/{id:Int}" page={ProductPage} name="product" />
        <Route path="/products" page={ProductsPage} name="products" />
      </Private>
      <Route notfound page={NotFoundPage} />
    </Router>
  )
}

export default Routes

8. Securing the GraphQL endpoint

To allow only authenticated users to query our GraphQL endpoint, I modified the handler (in api/src/functions/graphql) to the following code:

export const handler = (event, context, callback) => {
  if(process.env.NODE_ENV === 'production' && context?.clientContext?.user == null){
    return callback(null, {
      statusCode: 401,
      body: "Unauthorized"
    })
  }
  return (createGraphQLHandler({
    schema: makeMergedSchema({
      schemas,
      services: makeServices({ services }),
    }),
    db,
  }))(event,context,callback)
}

In Netlify add NODE_ENV to production to the environment variables.

Unfortunately, when working locally, context.clientContext.user is always set to undefined even with a Bearer. To work around this issue, we validate that the NODE_ENV is in production and only check for authentication in production.

This also means we need to move all the devDependencies of our package.json files to production dependencies. This is needed because otherwise, Netlify won't have the necessary packages to build our app.

api/package.json should now look like this:

{
  "name": "api",
  ...
  "dependencies": {
    "@redwoodjs/api": "^0.6.0",
    "jsonwebtoken": "^8.5.1"
  }
}

web/package.json should now look like this:

{
  "name": "web",
  ...
  "dependencies": {
    "@redwoodjs/auth": "^0.6.1-canary.26",
    "@redwoodjs/router": "^0.6.0",
    "@redwoodjs/web": "^0.6.0",
    "netlify-identity-widget": "^1.6.0",
    "prop-types": "^15.7.2",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  }
}

package.json should now look like this:

{
  ...
  "workspaces": {
    "packages": [
      "api",
      "web"
    ]
  },
  "dependencies": {
    "@redwoodjs/core": "^0.6.0",
    "netlify-plugin-prisma-provider": "^0.3.0"
  },
  ...
}

9. Integrating your new CMS with Snipcart (optional)

Let’s push this demo a bit further by integrating our shopping cart to the CMS we just created.

You’ll first need to create a new file in api/src/functions, let's call it products.js.

Inside this file add the following code:

import importAll from '@redwoodjs/api/importAll.macro'
const productsService = importAll('api', 'services').products

export const handler = async () => {
  const products = await productsService.products()
  return {
    body: products
  }
}

Netlify will create an AWS Lambda with this code that will return all the products. It will allow Snipcart to fetch from this endpoint and add the products to its database.

And that’s it! You now have the underlying infrastructure to build a custom e-commerce app powered by your own content management software.

Live demo & GitHub repo

jamstack-framework-demo

Try the live demo here (You’ll find the credentials on the demo’s landing page)

See the GitHub repo here

Closing thoughts

Since RedwoodJS is still in the early development phase, the documentation isn’t 100% comprehensive yet. It’s understandable but slowed me down a bit. I spent a bit over a day to build the demo. Another downside for me is that it doesn't support Typescript yet.

Where RedwoodJS really shines at this stage is on the scaffolding part. Adding a table to the database and creating the pages for simple CRUD operations took mere minutes.

Of course, I could have built a frontend store as an extension to my demo, always using Redwood. I thought it might be overkill for a single post. The tutorial by Richard Haines I’ve linked above does a pretty good job at covering that part if you’re interested ;)

Even though it’s not there yet, I think RedwoodJS has excellent potential overall. It’s a great tool to scaffold a project rapidly without needing to build the API. It brings a great alternative to other existing tools in the Jamstack community, and I have no doubt we’ll come back to it in the future!


If you've enjoyed this post, please take a second to share it on Twitter. Got comments, questions? Hit the section below!

Suggested posts: