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?
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:
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
Prerequisites
Basic knowledge of React
Node v12 or higher
Yarn
A free Snipcart account—If you want to follow the e-commerce side of this demo
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:
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:
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.
Voilà! Your simple CRUD CMS should now be working effortlessly.
6. Deploying the CMS
Deploying your Redwood project requires a few things:
Create a Netlify account (used to deploy our project)
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”:
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:
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
Try the live demo here (You’ll find the credentials on the demo’s landing page)
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!