
Learn with Jason on RedwoodJS 1.0 with Anthony Campolo
Anthony Campolo demos building a blog app with RedwoodJS 1.0, showcasing its new features and deployment options to Netlify and Render
Episode Description
Anthony Campolo demonstrates building and deploying a full-stack blog with auth using RedwoodJS v1 release candidate on Learn with Jason.
Episode Summary
In this episode of Learn with Jason, Anthony Campolo returns to walk through building a full-stack application using the RedwoodJS v1 release candidate. The conversation begins with Anthony's background as a music teacher turned developer through bootcamps and open source, before diving into what makes Redwood appealing: its batteries-included, convention-over-code approach inspired by Ruby on Rails. Key improvements in v1 include self-hosted database authentication (replacing the earlier dependency on Netlify Identity), the ability to run on a traditional server instead of being limited to serverless functions, built-in SEO meta tags, accessibility features like route announcements for screen readers, and support for six-plus deployment targets. The hands-on portion moves quickly from defining a Prisma schema and provisioning a PostgreSQL database on Railway, to scaffolding a full CRUD admin interface and generating a homepage with a GraphQL-powered cell — all in roughly 15 lines of custom code. Anthony then walks through setting up dbAuth, explaining how password salting and hashing work and why developers should trust frameworks rather than rolling their own security. The episode wraps with deploying the app to both Netlify (serverless) and Render (server-based), demonstrating Redwood's "universal deployment machine" philosophy, and a broader discussion about why convention-driven frameworks benefit teams shipping production code at scale.
Chapters
00:00:00 - Introduction and Anthony's Background
Jason welcomes Anthony Campolo back to Learn with Jason, noting it's been a while since his last appearance. Anthony shares his journey from music teacher to developer, explaining how he broke into tech through a bootcamp and open source contributions. He became the de facto developer advocate for RedwoodJS, which eventually led to his first tech job at StepZen, a GraphQL API company. The conversation highlights how contributing to open source can serve as both a learning tool and a career pathway.
Anthony emphasizes his belief in leveraging open source for building connections and skills, acknowledging that while his path worked out well, it's a viable route others can follow with dedication. Jason expresses appreciation for the career-switcher narrative and the idea that someone can go from no tech experience to gainful employment within a year through bootcamps and community involvement.
00:02:13 - What Makes RedwoodJS Appeal to Developers
Anthony explains what drew him to Redwood during his bootcamp days, where learning full-stack development with React, Express, and PostgreSQL simultaneously was overwhelming for a beginner. Redwood offered a framework with conventions that simplified the entire process from frontend to backend, including auth and database access. He compares it to Ruby on Rails, noting the shared philosophy of providing an opinionated, batteries-included development experience with co-creator Tom Preston-Werner's Rails background.
Jason and Anthony discuss the trade-offs of choosing Redwood over frameworks like Next.js or Remix. While Redwood is more opinionated about its backend stack — baking in Prisma for database access and generating auth code through CLI commands — the benefit is a more cohesive development experience. The flip side is that developers must buy into Redwood's chosen libraries, though the framework offers both first-party and third-party options for things like authentication providers.
00:05:21 - What's New in RedwoodJS V1
The discussion turns to the v1 release candidate, with Anthony explaining that the release candidate represents a stable contract with no breaking changes expected before the official launch. He teases an upcoming launch week with partner integrations, though no firm date has been announced. Jason notes the timing works well for a sneak preview on the show, since the last Redwood episode was from December 2020.
Anthony highlights the major v1 improvements: self-hosted database authentication replacing the earlier Netlify Identity dependency, the ability to run on a traditional server rather than being forced into a serverless architecture, built-in meta tags for SEO, pre-rendering for single-page applications, and accessibility enhancements like route announcement components for screen readers. He also mentions that Redwood now supports deployment to six or more platforms including Netlify, Vercel, Render, Fly, Layer0, and Flightcontrol.
00:09:10 - Deployment Options, SEO, and Accessibility
Anthony details the expanding deployment options, noting that the framework now supports everything from fully managed platforms to self-managed AWS setups through tools like Serverless Framework and Flightcontrol. Jason picks up on the interesting fact that meta tags and SEO are built into the framework rather than requiring plugins or manual boilerplate HTML editing, which Anthony confirms along with pre-rendering support for single-page app challenges.
The conversation shifts to accessibility features, particularly the route announcement component that helps screen readers properly detect page changes in single-page applications. Anthony credits team member Dom for researching solutions from Next.js and Gatsby and baking them into the Redwood framework. They also discuss Redwood's ideal use cases — data-intensive apps, dashboards, and applications with user accounts — rather than simple static sites like blogs, despite the tutorial using a blog as its example.
00:14:30 - Setting Up the Project and Database Schema
The hands-on portion begins with Anthony guiding Jason through forking a starter repo, installing dependencies with Yarn (which is required for Redwood's workspace support), and exploring the project structure. They define a simple Prisma schema with a Post model containing title, body, and createdAt fields, switching the database provider from SQLite to PostgreSQL. Anthony walks through each field definition while GitHub Copilot helpfully autocompletes parts of the schema.
They provision a PostgreSQL database using Railway, a hosting service Jason hasn't used before. After adding the connection string to an environment file, they run a single Prisma migration command that creates the database table. Jason marvels at how far developer tooling has come compared to his early days of SSH-ing into production boxes and running MySQL commands directly, noting how much nicer the modern workflow is with visible SQL output for review.
00:24:46 - Scaffolding CRUD and Generating Pages
With the database ready, Anthony introduces Redwood's scaffold command, which generates a complete CRUD admin interface from the Post model — including GraphQL SDL definitions, Prisma-based services for create, read, update, and delete operations, and React components. They kick the dev server back on and immediately have a working admin UI at the /posts route where they can create and edit posts, all without writing any frontend code manually.
Next, they generate a homepage and a "cell" — Redwood's abstraction that combines a GraphQL query with loading, empty, failure, and success states for data fetching. After a few small edits to wire the cell to the homepage, posts appear on the site. Jason tallies up the work: with roughly three commands and about 15 lines of custom code, they have a functioning blog with database-backed posts, an admin dashboard, and a public homepage displaying content.
00:32:13 - Adding Database Authentication
Anthony points out the app isn't done because anyone could access the admin and create posts without authentication. They run Redwood's setup auth command for dbAuth, then examine the git diff to see all the changes: forgot password options, login forms, password hashing utilities, role-based auth checks, and an auth provider wrapping the app's routes and Apollo provider. Jason appreciates that the auth setup focused on backend logic rather than rebuilding the UI.
The conversation takes an educational detour into password security, where Jason and Anthony explain hashing, salting, and rainbow tables. Jason shares his own early-career embarrassment of misunderstanding these concepts and emphasizes why developers should trust proven framework implementations rather than rolling their own security. They add a User model to the Prisma schema, run the migration, wrap admin routes in a Private component, configure GraphQL directives to allow public read access while requiring auth for mutations, and generate login and signup pages.
00:48:03 - Configuring Auth and Locking Down Signups
After wiring up the authentication UI with hooks for login, logout, and checking the current user, they test the flow: signing up, logging in, and verifying that unauthenticated users get redirected away from admin routes. Anthony shows a quick trick for locking down the app after creating an initial user — changing the signup handler to return false, effectively preventing new account creation while still allowing existing users to log in.
Jason briefly explores adding a post detail page with route parameters, demonstrating how Redwood handles dynamic routing with bracket syntax for URL parameters. While they don't have time to fully build out the feature, Anthony points to the routing documentation for next steps. The segment shows how Redwood's conventions make it straightforward to understand what code goes where, even for features that weren't generated automatically.
00:55:46 - Deploying to Netlify and Render
The deployment phase begins with setting up Netlify using Redwood's deploy setup command, which configures the Redwood TOML and generates a Netlify TOML with the correct build commands, publish directory, and function redirects. Jason uses the Netlify CLI to initialize the project, import environment variables, and push the code, resulting in a successful serverless deployment in about two and a half minutes.
They then create a separate branch for deploying to Render as a server-based alternative, configuring a render YAML file with proper API URL routing and adding CORS settings required for the cookie-based dbAuth to work across separate web and API services. While the Render deployment encounters a 404 due to a domain configuration issue they don't have time to fix, the exercise demonstrates Redwood's universal deployment philosophy and the differences between serverless and server-based hosting approaches.
01:17:23 - Convention vs. Flexibility and Wrap-Up
With the Render build running, the conversation shifts to a thoughtful discussion about convention-over-code frameworks versus flexible approaches. Jason draws parallels between Redwood, Rails, and Angular, arguing that generators and conventions create extreme portability of developer skills — any Redwood developer can walk into another Redwood codebase and immediately understand its structure. He contrasts this with plain React projects where each team creates unique file structures and naming patterns.
Jason makes a case for convention-driven frameworks being especially powerful for teams where developers aren't primarily web specialists, citing his experience at IBM where engineers building UIs as a secondary responsibility benefited enormously from strong defaults. Anthony shares resources for next steps including the RedwoodJS documentation site, Discord community, Discourse forum, Twitter account, and a revived newsletter featuring community spotlights. The episode closes with the Render deploy going live and encouragement for viewers to explore Redwood's growing ecosystem.
Transcript
00:00:07 - Jason Lengstorf
Hello, everyone, and welcome to another episode of Learn with Jason. Today on the show, we're bringing back Anthony Campolo. How you doing, Anthony?
00:00:16 - Anthony Campolo
Hey, I'm doing great. Thanks so much for having me back.
00:00:19 - Jason Lengstorf
Yeah, happy to have you back. It's been a minute. So for folks who aren't familiar with you — and actually, you know what, I think the last time you were here, you had a different job, so maybe we should...
00:00:28 - Anthony Campolo
I didn't have a job at all.
00:00:29 - Jason Lengstorf
Oh, you did? All right, then. Let's talk about what's changed. Want to give folks who aren't familiar with your work a bit of a background?
00:00:36 - Anthony Campolo
Sure. I was a music teacher who switched careers into tech via the bootcamp route, contributing to open source along the way. When I was on the show last time, it was December 2020, and it was actually the day I got onto the Redwood core team. That was basically how I was breaking into the industry — by becoming the de facto developer advocate for Redwood. The month after that is when I actually got my first tech job, working for StepZen, a GraphQL API company. You actually had Carlos on quite a few months ago now, so that was awesome. I'm a very big proponent of open source and leveraging it to both learn things, make connections, and possibly get a job. I'm definitely lucky in how it all worked out, but I think it's a path that others can take if they really want to.
00:01:35 - Jason Lengstorf
Yeah, for sure. I love the story of the career switcher — being able to see some potential and decide to go to a bootcamp and, within a year in a lot of cases, go from knowing nothing about tech to being gainfully employed as a developer or somewhere else in tech. So you said that one of the first things you did was get into Redwood as the de facto devrel. What drew you to Redwood?
00:02:13 - Anthony Campolo
Yeah, there are a couple things. The main one is that when I was in my bootcamp, I was learning a full-stack web development curriculum that was very heavy on React — building a full-stack React app with a React front end talking to an Express/Postgres backend that was managing auth, and then you had to deploy that somewhere. It was very challenging to do as a beginner because you're learning to code, learning what programming languages are at all, learning JavaScript, learning SQL, and figuring out how your development machine works. All of this combines to be incredibly challenging. Redwood basically says, "Hey, we're going to give you a framework with conventions that make this all much easier — that makes sense front to back in how auth is managed and how the database is accessed." It wasn't a new idea. It's very much comparable to Ruby on Rails. Tom Preston-Werner, who built GitHub with Rails, is a co-creator, so there's a lot of crossover there.
00:03:13 - Anthony Campolo
At the time I was just like, I just need a thing that works. And it was a thing that worked in a way that wasn't really possible for me at the time with the skills and technologies I knew.
00:03:25 - Jason Lengstorf
Yeah, the appeal of something like Rails is that it's batteries-included, and Redwood has a lot of parallels with that, as you said. So for somebody evaluating frameworks, what would you say are the big trade-offs of Redwood? What do you gain and what do you lose by choosing Redwood versus other tools?
00:03:48 - Anthony Campolo
The main things are that you're going to have a more opinionated backend than you would with a framework like Next or even Remix. Remix is more full-stack and gives you the ability to hook into databases with Prisma, but they don't bake it into the framework the same way Redwood does. With Redwood, Prisma is built into the Redwood CLI — you run commands that look like Redwood commands but they're really aliasing Prisma. With something like Remix you install Prisma and set it up yourself. It's the same with auth: you can set up auth very easily in Remix, but with Redwood you run a command that will actually write your auth code for you. So you have to be more bought into whatever solutions they provide. The flip side — which is really nice — is that they have both homegrown versions and third-party versions of things like auth. Like what we're going to do today: self-hosted, managing our own auth. But if you want to use a third-party service, you can do that.
00:04:52 - Anthony Campolo
The trade-off is that you have to be bought into whatever tech they've chosen and trust that they've selected the right libraries. That comes from the experience of the team, the community, and the startups building on it. There are a lot of eyes on it, a lot of flywheel effect going on. I think it's in a good place right now and it's a good time to learn it, because we are approaching V1.
00:05:21 - Jason Lengstorf
Yeah, "approaching" — it sounds like you're at V1. We're going to be working with V1 today. It's just a release candidate instead of the final release. But a release candidate usually means we're within days or a week or two of something going live, right?
00:05:39 - Anthony Campolo
Yeah. With the release candidate, the idea is it's a guaranteed contract that there won't be breaking changes from here — whatever you're writing on the release candidate, you should basically just be able to bump the version number once the final release is available and it'll just work. We're going to have a launch week type thing coming up where we'll have a lot of our partners coming together to talk about how you integrate Redwood with different things. There's a lot of people who've built a lot of stuff in a lot of different pockets and niches. That's coming up and hasn't been announced yet, so there's no hard date, but it's very soon. I just don't want to jump the gun on any of that.
00:06:22 - Jason Lengstorf
Sure, sure, sure.
00:06:23 - Anthony Campolo
We're setting up a big release for V1, so the code stuff is basically settled right now. It's just last-minute bug fixes and stuff.
00:06:33 - Jason Lengstorf
Gotcha, yeah. I'm not sure how this happened, but it just lined up really well — we're getting a lot of sneak previews on Learn with Jason these last few weeks. I'm really excited to get a sneak preview of what we're getting into with 1.0, because we did a Redwood episode before. When did that one happen?
00:06:59 - Anthony Campolo
It was December 2020.
00:07:00 - Jason Lengstorf
December. So about a year and a half ago now. What's new from that episode that's particularly notable in 1.0?
00:07:14 - Anthony Campolo
There's a lot, but the things we're going to focus on today are being able to actually manage your own auth. When it was first set up, it used Netlify Identity as the auth solution. A lot of people don't necessarily want to buy into a specific service for auth. So the main thing is you have your own setup where passwords are salted and hashed, and Redwood does all of that for you. The other big thing is that you can now actually run it on a server. This is kind of funny because Redwood was originally proposed as a serverless-first framework — you would never need to run a real server. But the problem is Redwood is by definition a serverless anti-pattern because it's a lambdalith.
00:08:11 - Anthony Campolo
It's one giant Lambda monolith: a GraphQL server that you shove into a single Lambda. That doesn't scale. If you end up pushing the boundaries of what you want to do in production, at a certain point you really want to run it on a server. So they gave people the ability to just run it on a server very easily without having to change anything.
00:08:31 - Jason Lengstorf
Chat, we now have an excellent idea for fan art. What does the lambdalith look like? Serverless is great, but if you're trying to shoehorn a full server into serverless functions, are you getting any benefit out of it? So being able to have a server-based version makes sense. So you can host it on a server, it's got different auth solutions — any other notable features we should call out here?
00:09:10 - Anthony Campolo
There are little things like meta tags and more SEO-related stuff, but there are also a lot more integrations with other providers. The number of ways you can host it is just insane now: Netlify, Vercel, Render, Fly, Layer0, Flightcontrol — those are the main ones. That's six different ways to deploy it.
00:09:38 - Jason Lengstorf
And that's before you start looking at roll-your-own solutions like AWS or GCP or things like that.
00:09:44 - Anthony Campolo
Serverless Framework is actually one that I wasn't being fair to.
00:09:47 - Jason Lengstorf
Yeah, Serverless Framework.
00:09:48 - Anthony Campolo
Yeah. That deploys straight to AWS, and actually Flightcontrol does the same thing — it gives you actual AWS infrastructure. So there's the whole spectrum: from totally managed to you-manage-almost-everything, and everything along that spectrum. There's something for everyone at this point, which is really cool. It was proposed as a "Universal Deployment Machine," a term Tom used a lot. This is the fruition of that — once you have all these different ways to deploy it, it's really interesting.
00:10:23 - Jason Lengstorf
You just said something interesting: you called out meta tags and SEO as being a feature of the framework. When I think of frameworks I'm usually thinking I'm going to need to get a plugin or a third-party tool, or I'll have to roll my own. When I need to add meta tags, Twitter cards, OG data, and things like that, I'm always opening up the boilerplate HTML file and dropping in my own stuff. Are you saying Redwood bakes that into the framework?
00:11:04 - Anthony Campolo
Yeah, there's a meta tag component where you can just pass in name, description, anything like that. And this goes along with pre-rendering for the front end — there's stuff to alleviate some of the challenges that come with single-page applications.
00:11:21 - Jason Lengstorf
That's really cool. Are there other built-ins like that — SEO and meta — that might surprise devs who are used to working with Next or solutions where you kind of have to roll your own?
00:11:35 - Anthony Campolo
Yeah, definitely. Another one is accessibility. Dom has worked a lot on this — when you try to navigate in a single-page app, it can confuse screen readers because they may not know whether the page has changed or not. There's a route announcement component that got baked in so that when you're switching between pages, your screen reader doesn't break. Things like Gatsby and Next do have this now, but a couple of years ago it was still a huge unsolved problem. Dom was looking at research from Next and Gatsby on how to make single-page application routers more accessible, and that's now baked into the framework as well.
00:12:21 - Jason Lengstorf
Nice, that's excellent. Do you have a core set of use cases that Redwood really excels at? What are the best use cases, and conversely, when might you not want to look at Redwood?
00:12:45 - Anthony Campolo
What's funny is we use a blog as the main example in the tutorial, but a blog is not really a good thing to build with it. It's more for dashboards, data-intensive apps, apps with users, apps that have a lot of interactivity and are database-backed products. That's really what it's aimed at. Having users is something I emphasize a lot because of how much auth is baked in.
00:13:19 - Jason Lengstorf
Nice. Okay, cool. So there's a lot we could dig into in the abstract, but it's feeling like maybe the right thing to do is just dig in and get our hands dirty. So why don't we switch over to camera two? Camera two — we just got booped. Interesting. All the boops that got dropped earlier queued and then dropped as we opened this up. That's fun — I've never seen that happen before. All right, lots and lots of boops happening. Thank you, Ben, for all of those. This episode, like all episodes, is being captioned by White Coat Captioning. We've got Amanda here with us today. Thank you very much, Amanda. And that is made possible through the support of our sponsors: Netlify, NX, and Backlight, all kicking in to make this show more accessible and to help us do fun things like build a boop drop. Thank you all very much for that. We are talking to Anthony today — you can find Anthony on Twitter here. Make sure you go and give a follow.
00:14:30 - Jason Lengstorf
And we are talking about RedwoodJS. I think that's all the things. So what should I do next if I'm ready to start?
00:14:45 - Anthony Campolo
Yeah. I have a repo set up for you that you can fork and just start working off of.
00:14:51 - Jason Lengstorf
That's this one you sent me ahead of time. I'll drop this into the chat if anybody wants to follow along. And then I'm going to hit this fork button.
00:15:01 - Anthony Campolo
This is going to be pretty much what you'd get from running Create Redwood App, but it's upgraded to the release candidate. In the readme it says if you want to do that from scratch, you just run this command and that will bump you up to the release candidate.
00:15:16 - Jason Lengstorf
Got it, the RC. Yep, okay.
00:15:20 - Anthony Campolo
Just clone it down and run yarn install and that'll get your dependencies going. One thing worth noting for people seeing this for the first time: Yarn is required for Redwood because it predates NPM workspaces. So all your commands will be yarn rw followed by what the command is.
00:15:47 - Jason Lengstorf
All right, so there it is.
00:15:50 - Anthony Campolo
Nice.
00:15:50 - Jason Lengstorf
Forgot that I switched to Volta on this. I hadn't used it before very recently. It's great, I like it. Here's Yarn installing our packages. I did a quick ls over here so we could see what's going on. We've got a node version, prettier config, Redwood TOML, lock file, testing setup, a GraphQL setup, and environment variables over here. API folder, web folder, scripts folder.
00:16:26 - Anthony Campolo
You don't really need to mess with most of that. The main things are the web folder and the API folder, which is where all the code we're going to work with will be.
00:16:35 - Jason Lengstorf
Here's our API folder. It has a server config, source — database, a Prisma schema. Seeing Prisma show up as built into more and more frameworks, which is interesting. Cool to see them grow. Here's source, app, routes. In the abstract I understand what's going on here. What should I do to get started? Should we just start this thing up and roll?
00:17:10 - Anthony Campolo
Yeah, start by running your development server: yarn rw dev. That'll kick off the dev server. Since we don't have any pages right now, it'll show a default splash page. Last time I was on here, we did a page and then worked backwards to the database, but this time let's start from the database and the API, because that's going to factor in a lot more to what we're doing today. The first thing we want to do is go to our schema.prisma file to create our schema. Change the database provider at the top from SQLite to PostgreSQL. For anyone who's never seen a Prisma schema: you're defining your database. You could use SQLite, Postgres, MySQL, MongoDB, or Microsoft SQL. Then we're going to define a model — rename the user example to Post.
00:18:12 - Jason Lengstorf
Am I keeping this user example?
00:18:14 - Anthony Campolo
Yeah, keep pretty much all of that for now — just rename userExample to post.
00:18:22 - Jason Lengstorf
Oh, gotcha.
00:18:23 - Anthony Campolo
Yeah. You want to keep the ID exactly the same — that's just giving you an auto-incrementing ID. Then change email to title and get rid of the unique constraint. Make that body instead of name, and keep it as a string.
00:18:46 - Jason Lengstorf
Yeah, and we probably want that to be required, right? Like, we don't want an optional body.
00:18:51 - Anthony Campolo
Yeah. Okay.
00:18:53 - Jason Lengstorf
And the question mark, for folks who haven't seen a Prisma schema file before — that's what makes a field optional.
00:18:59 - Anthony Campolo
Then we've got one more: the post will have a createdAt, which will be a...
00:19:03 - Jason Lengstorf
DateTime, createdAt.
00:19:06 - Anthony Campolo
Oh, and look — that's the whole thing.
00:19:08 - Jason Lengstorf
Copilot knows how to do it. Nice. All right, so we're defaulting it to now, so when you create the post it will pull a timestamp of the current moment. That's great.
00:19:20 - Anthony Campolo
Great. That's the whole schema we'll be working with. Now create a .env file at the very root of your project. If you just generated a project from scratch, you'd already have one, but because you cloned it down it doesn't have one — it's in your .gitignore, because you don't want to commit this file. This is where all the important stuff is going to go.
00:19:49 - Jason Lengstorf
Right. So here's my .env.
00:19:52 - Anthony Campolo
Great. The very top one — DATABASE_URL — that's what we're going to use. But first we're going to spin up a database. This is where we're going to use Railway. Railway is a really nice hosting service for databases or servers or anything you want to host. We're just going to spin up a Postgres database. Create a new project.
00:20:21 - Jason Lengstorf
New project, yeah.
00:20:23 - Anthony Campolo
And then just select Postgres.
00:20:26 - Jason Lengstorf
Postgres.
00:20:28 - Anthony Campolo
You've never heard of Railway?
00:20:31 - Jason Lengstorf
I've never used Railway before, no.
00:20:34 - Anthony Campolo
Yeah, it's been the way we've had the tutorial handle the database part because it's just a really, really easy way to get going. If you click Postgres, you'll be able to go to "Connect" and connect to your database. This will blur out your password, but you'll want to manage that off screen. Just grab the connection URL and put it in your DATABASE_URL.
00:20:59 - Jason Lengstorf
Got it. I want the connection URL, right?
00:21:06 - Anthony Campolo
Yes. It also gives a SQL connection.
00:21:10 - Jason Lengstorf
I'll move this off screen. There it is — Postgres with a username, password, and URL. Do I need to put anything else in this .env?
00:21:23 - Anthony Campolo
Nope, that's it.
00:21:25 - Jason Lengstorf
I'm going to close the .env and come back over. Are we going to be in Railway anymore?
00:21:34 - Anthony Campolo
We might want to pop back once we have data in it, but for now you can just leave it off screen.
00:21:38 - Jason Lengstorf
Don't need to worry about it too much. Cleared the sensitive data. So we're back over here. We won't need that anymore.
00:21:49 - Anthony Campolo
Yeah. Now let's go back to the terminal. Since we're adding environment variables, stop the server first. Then we're going to run yarn rw prisma migrate dev --name posts. This way it just does the migration without prompting for a name. This skips that step.
00:22:23 - Jason Lengstorf
Got you.
00:22:25 - Anthony Campolo
This is going to set up our database. It basically writes some SQL to create a table for the model and applies it to the database. And it also gives you the actual SQL it generated, if you want to look at that.
00:22:41 - Jason Lengstorf
Yeah, if we want to look. This is a cool thing about Prisma: it's not giving you secrets, it's just showing you what happened. If I, as someone who's not a DB admin, go in and make some changes, those changes can be reviewed by a DB admin who can say, "You shouldn't have done that," rather than me saying, "Well, I ran a command" and them having to go dig through source code to figure out what that command actually does. I like that you can see the SQL that was run. I've run it, and it looks like our database is now in sync with our schema. That's very cool — that one command, by defining our schema in Prisma, set up Railway for us because we put in that connection string.
00:23:32 - Anthony Campolo
Yeah, exactly. It creates a migrations table. If you go into here, you can see the Prisma migration and then the actual posts table.
00:23:47 - Jason Lengstorf
Wow, this is incredible. I come from a time before jQuery existed, and a lot of what I was doing was opening up old tools. I remember being in phpMyAdmin trying to use the LAMP stack and managing tables — I'd SSH into a production box and run MySQL inside the SSH session. I had no idea what was going on, I didn't know where I was. I felt like I was about ready to take down my whole system. Definitely did take down the whole system a couple times. This is just so much nicer. I can't believe how far this has all come.
00:24:32 - Anthony Campolo
And this isn't even the only option. Supabase is just as nice, and there are so many ways to host databases now — PlanetScale if you want MySQL. This is just one of a plethora of awesome options.
00:24:46 - Jason Lengstorf
Absolutely. Okay, so we've got a database. What happens next?
00:24:53 - Anthony Campolo
Now we're going to scaffold an actual admin UI in Redwood. We're going to run yarn rw g scaffold post. That's it. This takes the model and creates a whole CRUD UI. You got an SDL and services — if you go to posts, these are essentially Prisma commands: create post, update post, delete post. And then post.sdl.js is your GraphQL schema.
00:25:48 - Jason Lengstorf
All right, I see what's going on.
00:25:50 - Anthony Campolo
There's a whole bunch of React components and stuff too. But before we look at all that code, let's see what this actually does first. Kick the server back on: yarn rw dev.
00:26:05 - Jason Lengstorf
That little moment there brought to you by being willing to press up a thousand times rather than type another command.
00:26:12 - Anthony Campolo
And now go to /posts.
00:26:19 - Jason Lengstorf
Kidding. All right.
00:26:24 - Anthony Campolo
That's funny — it's the part that got you last time too.
00:26:26 - Jason Lengstorf
Yeah. This is my goldfish memory really coming into play because I have no memory of doing this before. Then we can edit it. Diggity dog, y'all. This is pretty incredible.
00:26:48 - Anthony Campolo
Yeah, pretty sweet. This is just us reaching into the API, and we didn't have to write any front-end code at all. Now what we're going to do is generate a page and use a cell to query it. We'll have to explain what that is once you actually have the code in front of us. And I just realized it's very loud.
00:27:14 - Jason Lengstorf
Sorry, is that a leaf blower?
00:27:16 - Anthony Campolo
Yes. Hopefully.
00:27:17 - Jason Lengstorf
The bane of video existence. All right, so I'm assuming I start with yarn rw.
00:27:26 - Anthony Campolo
Yep. Now we're going to generate a page this time. Do yarn rw g page home /. That will create a home page mapped to just /. If you're on example.com, that's what would come up.
00:27:46 - Jason Lengstorf
Okay, going back here — my web folder just changed. What did that change?
00:27:58 - Anthony Campolo
Pages, then HomePage, and HomePage.js. You also have a Storybook file and a test file as well.
00:28:08 - Jason Lengstorf
All right, special guest today: the leaf blower. All right, so with three commands so far and one schema definition, we've set up a database, configured that database with our schema, built an admin dashboard for managing posts, and created a homepage. I think the last dot to connect is getting the posts we created onto this homepage. I heard you say the word "cell."
00:28:49 - Anthony Campolo
Yeah. We're going to do one more generate command: yarn rw g cell BlogPost. Capital B and then blog, capital P then post. Yeah, the whole thing.
00:29:08 - Jason Lengstorf
Okay.
00:29:08 - Anthony Campolo
A cell is a GraphQL query combined with different states that your data fetching can be in: loading, empty, failure, or success. Pretty standard stuff for anyone who's done this kind of thing, but you don't have to write that conditional logic yourself. It gives it to you out of the box, so you have error handling built in.
00:29:32 - Jason Lengstorf
Very nice.
00:29:34 - Anthony Campolo
We only have to change a couple things. In the three places it says blogPosts for the query, change all three of those to just posts, all lowercase. Then in the query, add title, body, and createdAt at the top. That's the GraphQL query — you set it at the top and then in the success export you can render it however you want. It's just going to spit out some JSON for us right now. Save that, and now we need to import this onto our homepage.
00:30:14 - Jason Lengstorf
Okay, so I want to import... no curly braces?
00:30:22 - Anthony CampoloBlogPostCell. Yeah.
00:30:27 - Jason Lengstorf
Wait, how does that work? Because it's not a default export.
00:30:30 - Anthony Campolo
Babel magic is how.
00:30:33 - Jason Lengstorf
Babel magic. Okay, so I'm going to go back here and get BlogPostCell.
00:30:42 - Anthony Campolo
Yeah, and that'll be from src/components/BlogPost. Yeah, exactly. And just drop it in there.
00:31:04 - Jason Lengstorf
All right, cool.
00:31:06 - Anthony Campolo
You can also get rid of that top import line as well.
00:31:10 - Jason Lengstorf
Not using that anymore.
00:31:13 - Anthony Campolo
There's the meta tag thing I was talking about, by the way.
00:31:16 - Jason Lengstorf
That's very cool. Very handy to just have that put in there for you.
00:31:27 - Anthony Campolo
There we go.
00:31:28 - Jason Lengstorf
There's our post. Then if I come back here and create a new one — "second post," save it, come back out here — now we've got two posts. This is, in the simplest sense of the word, finished, because we now have the ability to create posts and read posts and display them on a page. And we did all that with — I lost count, but I think we've written about 15 lines of code.
00:32:13 - Anthony Campolo
Yeah. Not a lot, but it's not done, because we don't have auth yet.
00:32:18 - Jason Lengstorf
True.
00:32:18 - Anthony Campolo
We put this online, anyone could just go to that route and create posts. This is actually what happened the first time we built this.
00:32:24 - Jason Lengstorf
And we have learned: cannot trust the chat. They'll do the worst things — but I believe deep down that you're all good people, even when you try to prove that wrong. You dirty hackers.
00:32:42 - Anthony Campolo
All right.
00:32:42 - Jason Lengstorf
Okay, so let's add some auth.
00:32:44 - Anthony Campolo
Yeah. So now we're going to run a setup command. Setup commands are not quite like generator commands — they don't create as much code and files, they usually change some things in your project. This will be yarn rw setup auth dbAuth.
00:33:13 - Jason Lengstorf
Oh, dbAuth. Got it.
00:33:15 - Anthony Campolo
Yeah.
00:33:15 - Jason Lengstorf
Behold my bucket. All right, so this is the full command.
00:33:25 - Anthony Campolo
Yeah, that should do it.
00:33:26 - Jason Lengstorf
Do you want to talk me through what this does?
00:33:29 - Anthony Campolo
I have a better idea. You're already in a git repo — can you just commit everything you have right now, and then when we run this command, we'll look at the diff? This is a really good way to see what it actually changes.
00:33:43 - Jason Lengstorf
I'm going to commit and say, "Posts working, needs auth." Okay, cool. Now I'm going to run yarn rw setup auth dbAuth.
00:34:03 - Anthony Campolo
Yeah, there are other setup commands too — like a deploy one that configures for Netlify or Render. There are setup commands for auth and for deployment targets.
00:34:15 - Jason Lengstorf
Okay.
00:34:16 - Anthony Campolo
Overriding? Yes, you do want to overwrite. Yes. Cool. Now if we go back to our editor, as that's going, we'll start to see the diffs.
00:34:32 - Jason Lengstorf
This blew the whole thing away. Let's make this a little bigger. We get forgot password options, login options, reset password, sign up. There's a hashPassword. So we're hashing passwords, all right. Let's look over here. We get currentUser, check if they're authenticated, check if they have a role. We're getting role-based auth too. This is pretty cool.
00:35:10 - Anthony Campolo
Then app — this is your API, the final handler that it spits out. This is where you take the SDLs and services mapped all together. Then on the front end you have the auth provider wrapping your routes and your Apollo provider.
00:35:27 - Jason Lengstorf
Gotcha. Okay, so this did a lot without us having to do much. I've talked about this a lot — my beef with a lot of auto-generation is that sometimes it does all the things I don't care about and none of the stuff I want it to care about. People will auto-generate complex UI components and you have to go change all of it, and then they skip the parts you actually need. It's very much a "draw the rest of the owl" moment. What I love about this is that from what I'm seeing, it didn't really touch my UI. What it did was make it so that I need to be logged in to do certain things — it just wrapped everything in this auth handler.
00:36:27 - Jason Lengstorf
Is that right?
00:36:28 - Anthony Campolo
For now, yes. We will build out more UI stuff later. This is just one way to do it — you don't even have to use this approach. What it gives you is hooks: an isAuthenticated hook, a sign-in/sign-out hook. You can just use those hooks and build your UI totally from scratch. Or if you're using Netlify Identity or something like Clerk, those come with their own UIs. This setup is good for people who want to manage their own auth but don't really want to have to build the forms that go along with it.
00:37:08 - Jason Lengstorf
"It didn't touch the UI yet," says the chat. That's true. What's my next step?
00:37:16 - Anthony Campolo
The next step will be easier if you just grab this whole chunk of code from the readme. We have a User model to add to our Prisma schema. Scroll down past the stuff we already did. Yeah, right there — that's the User model.
00:37:43 - Jason Lengstorf
Okay, so let me get the User model and talk through this. We have an id, that makes sense. We have a name, it's optional. email needs to be unique — that makes sense, you only get one account per email. Got to store your hashedPassword and salt. Okay, so early on in my career I fully publicly humiliated myself by misunderstanding how salts, passwords, and hashes work. I tried to write an article about security that was so bad they had to take it down completely.
00:38:12 - Anthony Campolo
Right.
00:38:13 - Jason Lengstorf
Can we talk through a little bit about what this means? What does it mean to salt a password? And I am so sorry if I'm putting you on the spot here because...
00:38:21 - Anthony Campolo
No, it's — once you get the concept, I don't think it's really that complicated. The main thing is that we won't know the password. Once we create a user and look at our Railway database, we'll see that what's in the database is not the password we used to create it. We're able to take the password, run it through an algorithm that does cryptography on it to produce a key, and then produce more keys from that key. That's how you end up being able to verify the original password without having to actually store it in the clear. It's basically cryptography — how do you encrypt it, still access it, and verify that it's what you think it is without having to have it yourself? That's the purpose of it. It allows you to save the data in your database without having it exposed if someone tries to...
00:39:29 - Anthony Campolo
If someone hacks your database and sees what's in it, they won't necessarily get the passwords.
00:39:33 - Jason Lengstorf
Yeah, and the big thing about salts is this: I have a password — let's say my password is the word "secret." You shouldn't make that your password, but let's say it is. If we store that in a database and somebody gets that database, they just have my username and password right there. Cleartext is bad. The next step would be to use some kind of algorithm like MD5 or SHA-1 to turn that into a random string. Then, every time I log in, we run the hash again and compare it to what's in the database. But the problem is that if somebody figures out what algorithm we're using, they can brute force it — run every word in the dictionary, every combination of characters, until they find all the hashes. And then they can start guessing passwords.
00:40:35 - Jason Lengstorf
Once someone has generated what's called a rainbow table — which is essentially every possible output hash — they can look up if your hash is in that table, find the original password, and log in. What a salt does is give your app a random string unique to your app, so that even if I use the same password "secret" with two different salts, the hash that ends up in the database is different. This matters because now someone would have to redo all of that work for your specific set of hashes. Without a salt, anyone who cracked a given algorithm's full set of hashes could share that rainbow table and it'd be applicable to any app. The salt basically breaks that sharing. If any cryptographers are watching, I'm sorry for all the details I left out, but that's the general idea — a salt is just one added level of annoyance to somebody trying to crack your table.
00:41:48 - Jason Lengstorf
It makes cracking take longer and less likely to succeed. I hope you pass your CS exam tomorrow. Diatribe aside — I've copied this now, basically.
00:42:05 - Anthony Campolo
And the great thing about that whole segue is that you didn't have to code that yourself. The framework is salting and hashing the passwords for you.
00:42:17 - Jason Lengstorf
Yeah, this is something I think is really important. When I first started writing code, I remember building my own login and I got a lot of it wrong. I don't run any of that code in production anymore, but there's a decent chance that had somebody been motivated to, they could have cracked my security — because I didn't know the difference between MD5 and SHA-1, I didn't know how to salt properly. I was just thinking: I have to obfuscate what the plaintext password is in my database, that makes sense, nobody should be able to see the password. I didn't have all the extra layers of context about attack vectors and ways that people can get the original password. It's like pixelating text: you look at it and go, "Oh yeah, that's secure," and then you watch somebody who's actually into security immediately reverse-engineer what was pixelated and tell you exactly what it said. It's definitely a rabbit hole. It's very nice to be able to trust proven methods rather than trying to roll your own.
00:43:23 - Jason Lengstorf
Unless this is a space you want to make your career — security is a great career, there's so much room in anti-hacking and all that. There's a huge community there. If you're interested in that, there are great people on Twitter to follow who can help you get started. But if that's not what you want to do for your career, please trust the experts and don't try to roll this on your own, because you will be sad. Okay, so I've copy-pasted this User model in here. It's going to do all the work for us so we don't have to be good at security.
00:44:02 - Anthony Campolo
Now we need to run a migration to create the user table.
00:44:07 - Jason Lengstorf
That was yarn rw prisma migrate dev.
00:44:12 - Anthony Campolo
Yep, and then --name users.
00:44:23 - Jason Lengstorf
Off we go.
00:44:27 - Anthony Campolo
All right, now let's go to our routes file in the web folder. It's at web/src/routes.js.
00:44:39 - Jason Lengstorfweb/src/routes.js.
00:44:44 - Anthony Campolo
Yeah. In the top import, add Private. We're going to bring in the Private component and wrap the posts routes with it. We have our home route and then all the routes the scaffold created — new post, posts, post by ID, and so on. In the opening <Private> tag, add unauthenticated="home".
00:45:22 - Jason Lengstorf
Yeah, like that? Or like this?
00:45:26 - Anthony Campolo
Just the word "home" in lowercase. Just like that. What it does is redirect you — if you try to go to any of those routes, it kicks you back to home.
00:45:41 - Jason Lengstorf
Gotcha. So theoretically, if I start this right now, when I go to try to hit my posts route, it's going to bounce me to the homepage.
00:45:53 - Anthony Campolo
Yes.
00:45:55 - Jason Lengstorf
Let's try it. Off we go. There it is. Let me reload the page and see if I can stay here. Get out of here. "You don't have permission to do that." I like that it does that. That's fun.
00:46:25 - Anthony Campolo
Now let's make it so we still see posts on the homepage, because we don't want those to be private. This is going to be in our SDL, which is in the API folder.
00:46:39 - Jason Lengstorf
API folder.
00:46:41 - Anthony Campolo
This is also something new that wasn't here before. Under functions, you'll see graphql.js. These are directives, and this is because we're using GraphQL Helix and Envelop under the hood — or we may actually be switching to Yoga very soon. But the point is, we're using The Guild's GraphQL tools and there are a lot of built-in directives for auth and more. This is why we can't see the posts right now — @requireAuth is on both our queries and our mutations. So change @requireAuth to @skipAuth on the queries.
00:47:24 - Jason Lengstorf
Just on the queries. Right. So posts and post(id) — the ability to read all posts and get one post by ID — both of those will work without requiring authentication. That makes sense because once you've published a post, it's public information. We want someone to be able to see our published posts. But we don't want someone to be able to create, update, or delete posts without auth, which is why we keep @requireAuth on the mutations.
00:48:00 - Anthony Campolo
Yeah, exactly.
00:48:01 - Jason Lengstorf
Okay, got you.
00:48:03 - Anthony Campolo
Cool. And now let's run one more command: yarn rw g dbAuth. That will give you the login and signup pages.
00:48:21 - Jason Lengstorf
Happy authenticating. Okay, so yeah, it built some files for us.
00:48:30 - Anthony Campolo
Let's go back to the readme — this will be easier to grab a chunk of code from. Scroll down a little past what we just did. Yeah, right there — that's going to be our whole login setup.
00:48:44 - Jason Lengstorf
Okay.
00:48:48 - Anthony Campolo
Yeah, there you go. There are also a couple things you're going to need to import.
00:48:51 - Jason Lengstorf
Drop this in here. Okay.
00:49:03 - Anthony Campolo
You have to import useAuth.
00:49:13 - Jason Lengstorf
And that's autocompleted here, is that correct?
00:49:16 - Anthony Campolo
Yeah, that's the one.
00:49:17 - Jason Lengstorf
Okay.
00:49:18 - Anthony Campolo
And also import Link and routes from @redwoodjs/router. We need to link to the login page on our login button.
00:49:32 - Jason Lengstorf
Lowercase routes.
00:49:33 - Anthony Campolo
Yeah.
00:49:36 - Jason Lengstorf
Then we'll stack these properly and it looks like I need to...
00:49:41 - Anthony Campolo
Yeah. So from useAuth you're going to destructure isAuthenticated, currentUser, and logOut.
00:49:52 - Jason Lengstorf
I love autocomplete.
00:49:54 - Anthony Campolo
I know, right? I think that should be it.
00:49:58 - Jason Lengstorf
All right, let's give this a shot.
00:50:00 - Anthony Campolo
Actually, there's one other thing. Go to api/src/lib/auth.js.
00:50:09 - Jason Lengstorfapi/src/lib.
00:50:12 - Anthony Campolo
Yeah, and then auth.
00:50:14 - Jason Lengstorf
Okay.
00:50:14 - Anthony Campolo
Yeah. Go down to getCurrentUser and where it says select: { id: true }, add email: true as well.
00:50:27 - Jason Lengstorf
And what we're saying here — this is Prisma — we're specifying which fields to include. So we want the user's ID and their email. And we could also get their name and theoretically other things, but probably we shouldn't.
00:50:37 - Anthony Campolo
We want to be able to display the email on the page when someone's logged in.
00:50:42 - Jason Lengstorf
Right.
00:50:48 - Anthony Campolo
Right, yeah. Okay, that should be everything. Now let's do the thing and see what happens.
00:50:57 - Jason Lengstorf
All right. Here's our homepage. We can see the posts now because they skip auth, and we can log in.
00:51:10 - Anthony Campolo
Yeah.
00:51:11 - Jason Lengstorf
Okay. I don't have an account, so I'm going to sign up and set a username and password. I'm now logged in. Oh, and I said a username, not an email.
00:51:24 - Anthony Campolo
That's a little confusing because we give you an email field but the UI says "username." It's just a naming thing we need to work out. But this is fine because we don't necessarily need an email address to make this work — it's just a password and a username.
00:51:44 - Jason Lengstorf
Yeah. What will break on this is that all of our password reset flows are probably not going to work anymore because they'll try to send an email, but otherwise this is all fine. It's relatively straightforward to fix. I'd just need to update this. Is the signup form something I can touch or is it straight out of Redwood?
00:52:12 - Anthony Campolo
What are you trying to change?
00:52:13 - Jason Lengstorf
I'm trying to find the signup form I just used. If I wanted to update it to say "email" instead of "username."
00:52:20 - Anthony Campolo
Yeah, that's in the front end on one of the pages. I think it's the SignupPage.
00:52:27 - Jason Lengstorf
SignupPage, yeah. So I could go there and just update the label to "email," and I'd probably want to set type="email" on the input too, so the browser validates it.
00:52:51 - Anthony Campolo
Like a TypeScript type?
00:52:53 - Jason Lengstorf
No, I was just going to say type="email" because it's an input — that way the browser will yell at me if I try to put anything other than an email address.
00:53:02 - Anthony Campolo
Right. The forms come from React Hook Form. That's one thing — you can write regular HTML forms if you want, or use your own form libraries, but by default they give you React Hook Form.
00:53:21 - Jason Lengstorf
Somewhere in that library, if I go research it, I'll find how it works. Don't think I care about it right now. That's fine. So I know I'm logged in, which means I should be able to go to /posts now and create a new one. I can now edit. Okay, very cool. Then when I go back...
00:54:05 - Anthony Campolo
There is a problem with this, though — right now anyone can sign up and get an account and then start editing everything. There are two possible ways to go here. The long-term approach is to build in authorization — we have RBAC and roles built into the framework, and there's a detailed doc that explains how to do that. But what we can do right now is go into api/src/functions/auth.js, go down to signupOptions, and make the handler return false instead of db.user.create.
00:54:55 - Jason Lengstorf
Okay, so now that I've created my user...
00:54:57 - Anthony Campolo
You're going to take away the ability to create a new user. You'll still be able to log in with the user you created, but no one else can create a user right now.
00:55:11 - Jason Lengstorf
Okay, so now if I go back out — are we still running? We're still running. So I'm going to log out, log back in, try to sign up. And then it's like, "Nah, you can't do that." In a real app we'd obviously build more controls here. But I can still log in. So here I am.
00:55:46 - Anthony Campolo
Sweet. Now we are at the deploy step. We'll run another setup command, but this time: yarn rw setup deploy netlify.
00:55:56 - Jason Lengstorf
Hold on. I want to do one thing because I'm always curious how this step works. I'm going to go into the blog post cell. We've got BlogPostCell — this is returning one of these. I'm going to make this into a <pre> and add a link — I'll make it the item ID and say "view post." How do I make this work? What's the Redwood process for this sort of thing?
00:56:42 - Anthony Campolo
Yeah, so you'll need to add route parameters into the router file. If you look at the routes file right now, you can see how the posts routes are already set up.
00:56:53 - Jason Lengstorf
Router routes file.
00:56:56 - Anthony Campolo
Yeah. In the posts layout, we have posts and then {id}. You'll basically have something like that. This is covered in the docs — I'll drop the link in the chat.
00:57:16 - Jason Lengstorf
Does that page exist, the post show page?
00:57:23 - Anthony Campolo
This takes a couple of steps. Basically you just add /{id} in brackets after post.
00:57:40 - Jason Lengstorf
Okay, so I've got post/{id}. It looks like I can force it to be an integer if I want to cast it. And then the page would need something like this, which we're not going to build right now. But the page would get the route params — how would I pass in the ID?
00:58:14 - Anthony Campolo
It's similar to how you already use routes. So you'd have routes.post({ id }) — passing in the ID there.
00:58:35 - Jason Lengstorf
Okay, project for another time — covered in this doc here in the chat. Or wait, is this...
00:58:43 - Anthony Campolo
That's not the one.
00:58:44 - Jason Lengstorf
That's not the one. Here's the one — let me pull this up. This is the one: routing params. That'll show us how to do that. Okay, great.
00:58:56 - Anthony Campolo
You asked that last time I was on too. I was like, "We don't really have time for that."
00:59:04 - Jason Lengstorf
All right, so we are ready to deploy. You already told me what to do and I just stopped paying attention. Tell me one more time. Sorry.
00:59:12 - Anthony Campolo
Yeah, yarn rw setup deploy netlify.
00:59:19 - Jason Lengstorf
Okay, ready to deploy to Netlify.
00:59:25 - Anthony Campolo
And then let's look at our git diff and look at redwood.toml.
00:59:33 - Jason Lengstorfredwood.toml.
00:59:35 - Anthony Campolo
Yeah. What's happening here is we have the API URL — this is how the front end and back end know how to talk to each other. This is a GraphQL Lambda endpoint on your Netlify functions route. You can configure it to whatever you want, like just /api if you're hosting elsewhere. This configuration gets updated automatically by the setup deploy command. And it also generates netlify.toml.
01:00:09 - Jason Lengstorf
Yes. And this one has your build command, publish directory, where the functions live, dev setup — and redirects to send everything through the main function. Cool. So now if I run netlify dev, this should just pick up and run. Here it goes.
01:00:35 - Anthony Campolo
You're going to need two environment variables: the database URL and the session secret that dbAuth generated. You need both of those in the Netlify project. They're in your .env file.
01:00:53 - Jason Lengstorf
Okay. So check this out — I'm going to run netlify init, create and configure a new site on my team, and we'll call this "redwoodjs-v1." Let's go. I'll just let it import since it's already configured. Good. And then I'm going to do netlify env:import .env. Hopefully it's not going to show all of these on screen. It did. Dang it. All right, I'll have to go roll that secret. That's all now stored so that if I run netlify dev again, it pulls in the database URL and the session secret from Netlify, but because they're in the .env, the local file overrides. Very nice. Okay, give me just a second to roll that.
01:02:07 - Anthony Campolo
Actually, there's a command that will generate a new key for you.
01:02:11 - Jason Lengstorf
Okay.
01:02:12 - Anthony Campoloyarn rw generate secret. But you don't want to run that on screen.
01:02:17 - Jason Lengstorf
Right, right, right. Okay, there's my secret. I'm going to go to my .env — you dirty hackers, that one's already compromised, you can all look at it. Let me change this one. Then I need to get a new database URL because I screwed that up.
01:02:43 - Anthony Campolo
Railway has a button where you can hit "Wipe Plugin Data" in your settings and it'll wipe the whole thing and generate a new connection string.
01:02:51 - Jason Lengstorf
Oh, nice. "Wipe Plugin Data" — let me show this on screen because that's very cool. I went to settings and I'm hitting "Wipe Plugin Data." Wipe data. Okay, so I'm going to have to re-run my migrations. But I can go to Connect and grab the new connection string. Nobody can see my secrets.
01:03:18 - Anthony Campolo
You want the bottom one, not the top one.
01:03:20 - Jason Lengstorf
Oh, right, right.
01:03:22 - Anthony Campolo
The top one is actually cool, though — it's a whole command to set up psql.
01:03:27 - Jason Lengstorf
All right, so that's saved. Then I need to run netlify env:import .env to override those — clearing my screen. And now I can run yarn rw prisma migrate dev.
01:03:53 - Anthony Campolo
Yep, and just leave it without a --name flag because it already has the migrations saved — it's going to rerun those.
01:03:57 - Jason Lengstorf
Okay.
01:03:57 - Anthony Campolo
It'll say, "Okay, you have two migrations — I'm going to apply those to your database." Boom boom.
01:04:07 - Jason Lengstorf
Nice. Oh, dang it. Props to the Prisma team — this is very well done and very nice to use. Okay, so that's ready. I'm going to git add everything.
01:04:21 - Anthony Campolo
Great.
01:04:21 - Jason Lengstorf
And git commit -m "feature: ready to deploy." Let's push. And now if I open this up — there it is, it's deploying. And if I go to my site settings and environment, we can see those values are set through that CLI command. But it didn't start building, which makes me wonder if something went wrong. Let me check if the git setup is done. I didn't link a GitHub repository — I must have skipped a step. Let me go back here.
01:05:22 - Anthony Campolo
That's one of the things I still always go through the UI to do, copying and pasting the keys. There's where you put your keys.
01:05:30 - Jason Lengstorf
And also if you need them — I should have stayed there a second longer. There's a button to add environment variables, but I like feeling like a hacker. Makes me feel like I want to put my sunglasses on.
01:05:42 - Anthony Campolo
It's better for tutorials to have a CLI command because it removes the room for error.
01:05:52 - Jason Lengstorf
Now we are going to deploy. Let's play a memory game while we wait.
01:05:59 - Anthony Campolo
That's funny. I've never seen this.
01:06:04 - Jason Lengstorf
Yeah. How good is my memory? Let's find out. Oh, no. Did everybody see me nail that? All right, is it done?
01:06:46 - Anthony Campolo
There's something else we can do while this is going. If we go back to our project, we're going to create another branch for the Render deploy. Then we're going to run yarn rw setup deploy render --database none. What that's doing: we can tell it to spin up a Postgres or SQLite database on Render for us, but since we already have a database we don't want that. We're just going to use our existing database.
01:07:34 - Jason Lengstorf
Gotcha. So what we did before is the full serverless deployment on Netlify. What we're doing here is the server-based deploy. Render is similar to Netlify in that you create a git repo, connect it, push and it deploys — but it's for long-running servers. If you need a microservice, or in this case you want your server running for a long time, you use something like Render. I actually use it for the chatbot — the API uses a GraphQL subscription which requires a long-running server, and that runs on Render.
01:08:16 - Anthony Campolo
Nice. That created a render.yaml file. This is the only thing we need to make sure it knows how to handle the route redirect for your functions.
01:08:31 - Jason Lengstorf
Yeah, I just pulled everything off screen. Let me make sure I close that. Okay. So go into render...
01:08:38 - Anthony Campolo
Yeah. Next to destination, where it says "replace with API URL," change that to your Render API URL — something like https://redwood-lwj-api.onrender.com, depending on your project name. What this does is, when we spin up this project on Render it gives us that URL for the API. You just have to make sure your front end knows what it is. This will differ depending on your project name.
01:09:36 - Jason Lengstorf
Okay, got you.
01:09:38 - Anthony Campolo
Yeah. And then there's one other thing — CORS. This is a little obnoxious but you've got to do it. Go to api/src/functions/graphql.js. Under services, add a cors object with an origin property. The value is going to be the same URL you just wrote, except with web instead of api. And add credentials: 'include'. The reason we need to configure this is dbAuth — we're passing a cookie back and forth. If we didn't have any auth we wouldn't need to do this, but because we have the whole auth setup with cross-origin requests, we need CORS.
01:10:57 - Jason Lengstorf
We're moving things between the web and the API. So these need to be able to talk, and they're cross-origin because they're two different websites.
01:11:08 - Anthony Campolo
Yep. And then there's an auth.js file right next to graphql.js in your functions folder.
01:11:15 - Jason Lengstorf
Do I need to copy-paste something here?
01:11:17 - Anthony Campolo
Yeah, basically give it a similar CORS config, then go to authHandler.
01:11:25 - Jason LengstorfauthHandler.
01:11:26 - Anthony Campolo
That's the whole thing. Just drop the CORS config in there under authFields, make credentials: true — without quotes. Just like that. Then under cookie, change sameSite to none. I think that's everything on the back end. Then there's just one more thing in web/src/App.js.
01:12:04 - Jason Lengstorf
Okay.
01:12:05 - Anthony Campolo
Where it says AuthProvider, let's just grab the whole thing from the readme. Go almost all the way to the bottom. Yeah, that last block — just grab that whole app component and replace your current one.
01:12:26 - Jason Lengstorf
Okay. So I'm going to go here and go to App.js. What am I changing?
01:12:40 - Anthony Campolo
That whole component. What we're doing is adding config for credentials and also making Apollo happy.
01:12:50 - Jason Lengstorf
Got it. So basically because we're making cross-origin requests, we just need to update these two pieces. That makes sense. I'm not going to lie, I don't remember how to deploy to Render.
01:13:08 - Anthony Campolo
Luckily we're mostly set up. Just push this up onto the render branch you've got.
01:13:29 - Jason Lengstorf
This is done? Yeah, it's been done for a bit. How long did the first deploy take? Two and a half minutes. Follow-up deploys should be faster because things will be cached. Let me check — should all be running as expected. Login — this isn't going to let me sign up, right?
01:13:49 - Anthony Campolo
Yeah, you can't sign up. You can just log in.
01:13:57 - Jason Lengstorf
It's not hooked up to my database anymore because I didn't go back and — we'll have to fix that later. But for now this is doing what we want.
01:14:11 - Anthony Campolo
It shows empty, so it knows there's a blank database there. Otherwise it would show an error.
01:14:17 - Jason Lengstorf
Then I have Render here. This is my actual Render account. This is the Socket Studio API, which powers the stream overlay. I need to create new...
01:14:28 - Anthony Campolo
You're going to do "New Blueprint."
01:14:30 - Jason Lengstorf
New Blueprint, yeah.
01:14:33 - Anthony Campolo
This is because we have the render.yaml file. You might need to give it permissions to that specific repo via GitHub settings.
01:14:42 - Jason Lengstorf
Yep. I'm in too many orgs at this point. Install. Okay, let me pull this off screen for a second.
01:15:05 - Anthony Campolo
And...
01:15:11 - Jason Lengstorf
Okay, I'm logged in and I can choose this one now.
01:15:16 - Anthony Campolo
Give it the name "redwood-lwj-render."
01:15:27 - Jason Lengstorf
Okay. It's going to create both services.
01:15:30 - Anthony Campolo
Hit Apply. Then we just need to add in the environment variables. They actually let you feed it a .env file, which is kind of cool. But go to your dashboard.
01:15:42 - Jason Lengstorf
Am I doing something?
01:15:45 - Anthony Campolo
Yeah. Is it...
01:15:46 - Jason Lengstorf
Is it done?
01:15:46 - Anthony Campolo
This is a little weird — it started already and doesn't give you a lot of good feedback. Just go back to Dashboard.
01:15:53 - Jason Lengstorf
All right.
01:15:53 - Anthony Campolo
Yeah. Then go to the API service. This is where we put in the environment variables. Go down to "Secret Files," and then copy-paste what's in your .env into that.
01:16:12 - Jason Lengstorf
I just drop in the...
01:16:15 - Anthony Campolo
You want to do that off screen.
01:16:16 - Jason Lengstorf
All right.
01:16:20 - Anthony Campolo
One thing we should do in production — which we're skipping for the sake of the demo — is generate a different session secret for each deployment provider. But we'll just use the same one here because it'll be quicker and easier.
01:16:37 - Jason Lengstorf
Importantly, because it's not free, I'm probably not going to keep this Render deployment up.
01:16:43 - Anthony Campolo
That's actually not true — they have a free tier now.
01:16:46 - Jason Lengstorf
Oh, they do? That's great news. I didn't realize they had a free tier now.
01:16:49 - Anthony Campolo
It's very new — only a couple months old.
01:16:52 - Jason Lengstorf
Awesome. That's great news. I've saved my secret file here.
01:16:59 - Anthony Campolo
Cool. Just make sure the build reran — go to Events.
01:17:06 - Jason Lengstorf
Events. "Deploy canceled." "Another deploy started."
01:17:12 - Anthony Campolo
Cool. That's going to take a while — this thing takes about 10 minutes to build. Hopefully it'll be done before the hour is up. But yeah, questions, reactions?
01:17:23 - Jason Lengstorf
Yeah. If you've got questions, now's the time. What are you feeling?
01:17:33 - Anthony Campolo
Has things been coming back as you've been doing this, or did it feel like learning Redwood a whole second time?
01:17:40 - Jason Lengstorf
My experience with Redwood has actually been limited to the time I've spent with you, so I would say I remember the generate commands. What's interesting is Redwood embraces a school of thought that lines up with Rails and Angular — the idea of convention over code. You don't write code; you tell the generator, the CLI, what you want to do, and it generates most of your code for you, and then you do light customization. What that leads to is when I go into an Angular codebase, and presumably when I go into a Redwood codebase, things are going to be in the same places. Files are going to be organized the same way, the router is going to be set up the same way. I'm not going to have to try to figure out this team's particular style to piece together how their apps work, because so much of that is decided by the generation. It makes things very predictable.
01:18:46 - Jason Lengstorf
So the upside is just extreme portability of skill. If I become a Redwood dev, I can go to any team working with Redwood and be fairly familiar with the codebase before I even open it up, just because so much convention is handled by the CLI. I like that about generators — it gives you predictability and safety. The trade-off is that in a plain React codebase, people tend to do whatever they want: they set up their own file structures, their own naming conventions. Everything's organized differently, which means each React codebase is extremely flexible — you can build whatever you want in there — but each codebase is also an extremely unique animal that takes a little longer to get familiar with. If I'm working on a big team, especially one where people don't necessarily want to be web developers...
01:19:53 - Jason Lengstorf
When I was at IBM, a lot of people were writing UIs not because that was their job, but because it was a thing that had to happen as part of their other development duties. Having something that automatically sets them up with good practices, good code, and strong conventions is such a power-up for teams like that. I honestly think that's one of the reasons Angular is so popular in the enterprise — it provides that convention where things are predictable. You're not asking an engineer who's primarily working in Java and just got stuck with a UI ticket to figure out how to write a good UI. They just run ng generate this, ng generate that. And Redwood looks like it's solving that same problem in a way that's more batteries-included. Angular has struggled with some of this and I think they're working on solutions — I've heard rumors that some cool stuff is coming with Angular. But the power of being able to say to your team, "Just use the Redwood generator — it's going to look the way you want, it's going to have accessibility baked in, it's going to have best practices..."
01:21:02 - Jason Lengstorf
"You'll set the meta tags." That's really powerful for a team that just needs to ship production code quickly and has a million things on their plate. If you ask those people to lovingly handcraft code, they're going to take shortcuts and you're going to end up with problems. If you give them a generator like this, they're going to get to a good outcome. That's a big trade-off to consider when you're weighing which tool to build something with. But we've got some good questions here. Tony's asking: what generator is Redwood using?
01:21:43 - Anthony Campolo
Yeah, I dropped a bunch of links in response to that. It's using yargs. And you can — if you go into the Redwood codebase, you can create your own generators. At one point we were trying to create a generate generate command so you could actually generate your own generators. I don't think that ever fully worked out, but if you look at the code, you can see how we set up our generators and you can modify them. It's easy to contribute to — that's why it keeps growing to cover all these different things you can generate.
01:22:19 - Jason Lengstorf
Chaotic Good Boy — and I have to assume that avatar is a dog — says: it's funny that this criticism is levied against React by teams that chose Angular, but you can go wild with Angular too. That is true. With sufficient effort, you can make any codebase utterly off the wall. But I think the difference is where the defaults lie. What are you handed right out of the gate? With Redwood and Rails, what you're getting is a lot of "batteries included, you don't have to make this decision" guidance. When that's the default, it leads to — on average, because code cowboys will always exist — but on average you're going to get a really good outcome.
01:23:41 - Jason Lengstorf
And...
01:23:45 - Anthony Campolo
Rob is correcting me in the chat — we do have the generate generator command, but it's setup generator.
01:23:51 - Jason Lengstorf
Okay.
01:23:53 - Anthony Campolo
He said he lost the battle on the generate generate namespace — "generator generator."
01:23:59 - Jason Lengstorf
Great. We do have a generator generator. Okay, cool. Do I have any other questions? While we're waiting for this to finish deploying, where should people go for next steps? What resources should they be looking at?
01:24:26 - Anthony Campolo
Yeah, redwoodjs.com is a good place to start — it's been revamped and has consolidated docs and tutorials. There are two in-depth tutorials: one that walks through pretty much everything we did today, along with contact forms and layouts which we kind of skipped. The second tutorial goes into testing, Storybook, and all those other files that were being generated — because you're also automatically configured to run tests and set up Storybook. And then there are individual documentation pages for specific subjects like configuring environment variables and all the different deployment targets. All of that is now merged into one site.
01:25:13 - Jason Lengstorf
Nice. And I see Rob is saying this is a Docusaurus site — very cool. We'll drop this link in. Anywhere else people should check out?
01:25:25 - Anthony Campolo
It'll all be linked from redwoodjs.com, but we have a Discord, a Discourse forum, a Twitter account you can follow, and a newsletter that's just kicking back up. Let me grab the newsletter link.
01:25:43 - Jason Lengstorf
Nice. Let's go peek at Render — how's that going? "No pending migrations." Okay, so we've got some links dropping in here. You copy this one? So this is the newsletter?
01:26:24 - Anthony Campolo
Yeah. I'm not sure exactly how they set that up, but we had one over a year ago and this is our newly put-together one. The first issue has a "Heroes of Redwood" section at the very end — they highlight someone on the team and give them a chance to tell their story and talk about what they do.
01:26:53 - Jason Lengstorf
I love that. That's super fun. This is a cool place to go get information. And I assume this is probably one of the first places people hear about things unless they're actively watching the GitHub repo, right?
01:27:06 - Anthony Campolo
Yeah, the newsletter and Twitter would be the main places.
01:27:10 - Jason Lengstorf
Cool. All right, so I think we're almost done here. Oh wait — is that... does that mean done?
01:27:17 - Anthony Campolo
Hopefully. Check it out.
01:27:20 - Jason Lengstorf
All right. It says it's still in progress. Let me reload the page just in case.
01:27:24 - Anthony Campolo
Now it says it's live.
01:27:25 - Jason Lengstorf
Okay.
01:27:26 - Anthony Campolo
Yeah.
01:27:27 - Jason Lengstorf
And to see that...
01:27:28 - Anthony Campolo
You want to see the web one, not the API one. Go back to Dashboard — you'll see two separate services. One's a static front end and the other is an actual web service.
01:27:43 - Jason Lengstorf
Okay. It looks like Redwood doesn't like something about that API response. Let's peek at it and see...
01:27:56 - Anthony Campolo
We probably just messed up the domain.
01:27:58 - Jason Lengstorf
We messed something up.
01:27:59 - Anthony Campolo
It looks like...
01:28:02 - Jason Lengstorf
Response headers missing. It's straight-up 404ing. So we did something wrong. Is it because it needs to be a custom domain? That would mean the API isn't working.
01:28:21 - Anthony Campolo
Yeah, that's an issue.
01:28:23 - Jason Lengstorf
Okay, so we'd have to go fix that. We don't have time right now.
01:28:26 - Anthony Campolo
Yeah, I was trying to compress it all into a couple of steps. Normally you deploy it, get your link, then put your link into your config. I tried to jump the gun on that. And the original repo you forked from has the complete Render project as well. I'm pretty sure all the code is basically good — you just need to fix the domains.
01:28:43 - Jason Lengstorf
Yeah. If we get these domains fixed — the problem was a 404, not something else — so that'll clean it up. All right, with that being said, we're going to jump back out here and remind everyone that this episode, like every episode, has been live captioned. Amanda from White Coat Captioning has been here with us all day, and that's made possible through support from our sponsors — Netlify, NX, and Backlight — all kicking in to make this show more accessible to more people, which I appreciate very, very much. While you're clicking things, make sure you go check out the schedule. We've got Håkon coming on to teach us how to make an e-commerce site that gets a page speed of 100, which is a hard thing to do, and we're going to talk about how to do that using Remix and Crystallize. We've also got CommandBar, tRPC, dynamic images, a whole bunch of stuff. I've got things in my email that I haven't posted yet, so stay tuned. Lots of good stuff coming up.
01:29:37 - Jason Lengstorf
Make sure you go and give Anthony a follow on Twitter. Anthony, any parting words for the chat before we call this one done?
01:29:46 - Anthony Campolo
Just thank you for having me. I hope people check out Redwood. I've gotten a lot of use out of it over the years and really enjoyed the community — learning alongside everyone and getting to build cool stuff. So yeah, check it out.
01:30:01 - Jason Lengstorf
Awesome. All right, y'all, we are going to — oh, nobody's in live chat. Who should we raid? I'm going to take us offline here and call this one over, but let me know who we should raid and we'll go find somebody to raid. Anthony, thank you so much for hanging out. We'll see you all next time.