Tom Preston-Werner joins the show to talk about his latest project, RedwoodJS, and the decisions made about how it works, public APIs, how tied to Netlify RedwoodJS is, and why they're using Prisma.
Time Jump Links
MANTRA: Just Build Websites!
Dave Rupert: Hey there, Shop-o-maniacs. You're listening to another episode of the ShopTalk Show, a podcast all about front-end Web design and development. I'm Dave--in the house with the kids--Rupert and with me is Chris--in his nice, luxury office space in Bend, Oregon--Coyier. Hey, Chris. How are you?
Chris Coyier: Sorry. You could have this too, Dave. Just move to Bend after this is over. We'll get a nice office together. The family will play in the woods. You're going to love it.
Dave: I could probably live in your basement for a few years, so that works out.
Chris: Yeah. No basement. There's too much bedrock here. We're too close to the mountains.
Chris: There are no basements in Bend. Yeah, I grew up with a basement too. Anyway, we have a great guest on the show today and we're going to end up talking about probably stacks, full-stacks, JAMstacks, frameworks, the whole thing. It's Tom Preston-Warner. Hey, Tom.
Tom Preston-Warner: Hey, Chris. How are you doing? Hey, Dave. Glad to be on the show.
Chris: Yeah, thanks for coming on. You know, obviously, I think a lot of people know who you are but a resurgence of Tom with this RedwoodJS stuff. I think it's definitely worth talking about that. I find it absolutely fascinating. I've been watching it, looking at it. Even if you don't have an excuse to use it right now, I think it's worth watching just for the -- gosh, it's fascinating what's happening with all these technologies coming together. It almost feels like a--
Maybe it doesn't feel like it to you because you work on it all day, probably every day, but this kind of natural evolution of, like, at some point there's going to be best of breed tools for things like front-end frameworks, data storage, and stuff. The best of them will form this Voltron ball and exist in the world. Maybe that's what RedwoodJS is.
First, maybe, is that what's hot on your mind recently? Is this your biggest thing?
Chris: Mm-hmm. Mm-hmm. It's unavoidable, I think, that we're going to talk about opinionated frameworks versus unopinionated frameworks. Would you say it's fair to throw this on the opinionated side? It seems heavily so.
Tom: Yeah, that's where we started. I come from a Ruby on Rails background, so that was revelatory to me in my younger days. Redwood is the same. We make choices for you so that you don't have to make them.
We give you the happy path. If you want to stray from that path, we want to make that possible, but your happy path will be easier. The old saying: Make easy things easy and hard things possible. That's what we're going for.
Chris: Yeah, that's great. It seems almost more forthright about it on the homepage. For the podcast listeners, here's the laundry list: React, GraphQL, Prisma. I guess that's the three. Are there any more you'd throw on the list? That's what the homepage says, but that's front-end framework way to communicate with back-end and back-end.
Tom: Yeah, I think those are the big three technologies that you're going to key in on.
Chris: Yeah. But what about CSS? Is that where it stops? Do you stop caring at that point?
Tom: We started with the idea that we would heavily integrate style components, which I love and are amazing.
Chris: Okay. Yeah.
Tom: That would be part of the tech stack. But as we got into it, we started looking at -- one of the guys that joined the core was really into tailwinds, and so then we were like, "Well, should it be tailwinds or should it be styled-ccomponents? What should CSS look like?"
Then we realized that if we made a strong choice, we would be alienating some portion of the potential user base too much. There is no consensus on CSS frameworks on what to do about it. It's one of those things like semicolons. If you hard enforce no semicolons--and, to be fair, Redwood ships with the opinion that you shouldn't have semicolons-- but if you were to say, "You will never have semicolons. You can never ever have semicolons," then some portion of the world would say, "We have to have semicolons. Hard pass."
Chris: Yeah. Sure.
Tom: We don't want to be that, right? Opinionated means opinionated not dictatorial.
Tom: We chose not to be heavily opinionated on CSS framework currently, although, we do use tailwinds for the scaffolded components.
Chris: Okay. Yeah.
Tom: If you scaffold out something, then we do use tailwind for that.
Chris: Interesting. I agree that that's a bold one. Even React is a choice.
Chris: You're saying no Angular, so you've lost those people.
Tom: You almost have to make that choice. It's such an integral part of how it comes together. Either you're going to use React, you're going to use Vue, you're going to use one of the other ones. You're going to come up with something new. You need something to be how you write your front-end stuff. You could claim….
Chris: Yeah, I hate to call CSS trivial, but maybe.
Tom: Yeah, I mean CSS, though, is a layer on top of that. First, you need the structure and then you can style it. It is one level away from the content itself. It's integral to it. I'm not downplaying the importance of CSS but there is no consensus and I don't want to alienate people.
We are alienating Vue people, people that say, "I will only use Vue." We will alienate them. But looking at what's popular right now, looking at where people's minds are at, React has the bulk of the mindshare. To be honest, it's just where I come from.
Chris: Yeah. Sure.
Tom: We started using React at my current startup Chatterbug, and a lot of the design principles of Redwood come from the experiences that we've had there and wanted to codify. We just were building this front-end stuff and it's just too hard. We spent too much time picking technologies, trying to make them work together, and wondering why. Why is there not a framework that does this stuff for me?
Dave: Mm-hmm. Well, I think it's interesting. Somebody -- I was camping and I got back. We have tweets that are like, "You need to check out Redwood and have Tom on!" We were just like, "Well, okay. I guess something happened this weekend - a new framework." [Laughter] I went and I was like, "Oh, it's React."
Dave: Yeah, yeah.
Tom: --because that brings with it a lot of baggage and I love Rails. Rails is what made it possible for me to build GitHub. There would be no GitHub without Rails. I'm fairly certain.
I owe my success to Rails and so I don't want to rag on Rails but I have my problems with Rails. I don't want to write Rails anymore. I want to build in this newer way--
Dave: You end up with Ember, essentially, right?
Dave: Like you're doing it strict.
Tom: If you try to be -- I think everything new and revolutionary that comes out that is really, truly that isn't just X for Y. Like people like to say today. They're like, "Oh, I'm building the GitHub for whatever." It's like don't build the GitHub for whatever. Just build the whatever.
Tom: Make it its own thing. What is it that makes your thing special? It doesn't have to be the X for Y. I think that's too limiting. It makes you want to copy too much.
For Redwood, Redwood is its own thing. Even the name itself is counter to everything that I see. I'm just tired of everything being this physics-based, atomic thing, like React is an Atom thing and Atom is an Atom thing.
I don't know. Everything is all technical and physics and I love that stuff. I started as a physics major. It's my favorite thing ever, but it's just too much the same. You see a bore model of an atom as a logo and you're like, "That could literally be any of 16,000 different software projects."
Tom: I was thinking about it and this framework was actually originally called Hammer, but there is a project already called Hammer.
Chris: Yeah, there is.
Tom: So, I had to come up with a new name.
Chris: Yeah. Well, you had good luck with having a little cat, too, so that worked out.
Just getting Jest integrated, it's still in progress and it's going to take us a while because we do some fancy things for developer experience but you pay the price in the integration because Jest is like, "I don't know anything about what you're doing over here in Webpack," so now we have to teach Jest how do all those things that Webpack is doing for us at build time, right? This is not easy.
You, as an application builder, shouldn't have to spend your time doing that. When a framework is done well, the framework is doing the heavy lifting and all the really awful crap that you don't ever want to have to touch. Right? You take the hit.
Chris: What are those things, in this case? It's not just a smashing together of React, GraphQL, and Prisma. I guess it is, but it does fancy stuff. The first time I saw it was, Shawn Wang had a post that said, "React Single-File Components Are Here," was the title of the blog post.
Chris: I was like, "What?! React released single-file components? That's crazy." But they didn't, of course. What they were talking about was Redwood and your structure of how you expect components to be built.
When I think of single-file components, I think of the CSS aspect being in there, but whatever. Anybody can do that. But in your case it was kind of like these -- I don't know. Your expectations of how a component are built are a little different, right? That's what Redwood -- what's one thing that Redwood brings is this different style of component building.
Tom: Yeah, I'd say they're not radically different. They still look the same as you expect. It's mostly that we expect you to put them in a certain place in the directory structure. That's all--
Tom: There are opinions about that. "Here's where you put things," right? Like, "Why should I have to decide where to put files in my project? It's ridiculous. Just tell me where to put them. I'll put them wherever you want. Just tell me where to put them."
Chris: Sure. Sure.
Tom: We tell you where we put them.
Dave: That's half the battle.
Chris: Next is like that too, right?
Tom: [Laughter] Yeah, right. This is common for frameworks, right? It's all part of the decision-making process that you have to go through and it drains you if you have to do it all yourself.
Chris: It's not true of React. React itself doesn't care.
Tom: Well, that's the problem.
Chris: Your framework cares, right?
Tom: That's the problem with React. The problem with React is that it doesn't care. It doesn't care so much that it makes everyone do the work and everyone does it differently. Then let's say you want to go from one job where you're doing React to another job where you're doing React. You may as well be using different front-end systems because they can be so radically different the way that they build. Do you use hooks? Do you not use hooks? What's your CSS story? How do you do data fetching?
All of these things, all these little things end up being different. Redux, no Redux. React is one of many, and so I think there's confusion when people call React a framework because I don't think it's trying to be a framework. It's trying to be a rendering layer. It's trying to do one-way data rendering and do it really, really well, and it does. It does it amazingly well.
Tom: It's awesome, the things that it gives you, but it's not, to me, what I would think of as a framework.
Chris: It's not quite useful enough by itself, so it always comes with a bunch of friends.
Tom: Right, exactly. It's always React and friends. Then it's the "and friends" that makes it really hard to go from job to job.
One sort of aspirational goal of Redwood is that you could work one place and you're like, "Oh, yeah, we use Redwood. We build our app with Redwood." You work there and then you go to somewhere else and they're like, "Oh, yeah, we also use Redwood. We need a Redwood developer." You get in, you open up the application, and you're like, "Oh, this is Redwood. I know this."
Tom: In the same way that you can do that with Rails, for the most part. Obviously, when Rails apps get big, they end up diverging, but you at least know that some basic assumptions will be true.
Chris: Sure. I think that's true, too. I still use Rails. [Laughter] I almost use it more now, weirdly. Anyway, when you have a Rails 4 app and you've got to get it to 5 and then you've got to get it to 6, you can do it yourself but they're so similar that you can hire a specialist to do that. They'll roll in and do that and they don't even need to know your app all that well. They can just do it.
Tom: Yeah, it's because there are standards. There are standards of what it means to be a Rails app. There are standards of what it means to do things in a Ruby on Rails way, and there's some consensus around best practices and what are the most common plugins and things that you would use.
There is very little convergence in the React world. There's starting to be some. That's why I think you start seeing more frameworks right now. In fact, we're seeing several of them that I think are all trying to solve the same problem and that's great. Competition is good and the different approaches will be very interesting to see play out.
For us, we are focusing on, like you said, the stack that is React on the front-end and GraphQL as a communication layer and using Prisma as the way that you talk to your database on the back-end. That will become more sophisticated over time to give you more choice in how you do that, the number of types of databases that you can talk to.
Chris: Oh, I was curious about that. That's cool.
Tom: But also, there is this notion that, eventually, with Redwood, we can accommodate some of these opinions. It could be that because your front-end and your back-end are totally decoupled through GraphQL that you could swap certain things out or, from the very beginning, we've thought that Redwood is multiclient ready, we will say.
Right now, you'd say, "I'm going to start building a Web app because that's easy, fast, and I can get it done. It gets me something online." Then, oh, whatever you built starts to become popular. You say, obviously, "I need a mobile app.
What's your story for a mobile app? If you're working in Rails, your story is probably, "Oh, crap. Now we need GraphQL, and so let's build out a GraphQL API to serve our mobile app." Now you've built your business logic twice.
From the very beginning of a Redwood app, we ask you to think about your API and to think in an API mindset. That will future proof you in a way that allows you to build any type of client and still consume the same GraphQL API that is better designed because it is the only way that your front-end talks to your back-end. If you want to add a mobile client, you want to add a command-line interface, or you want to add -- let's say your thing works on a kiosk at a mall, all of these different things can all talk GraphQL. They're all just consumers.
Tom: From day one--
Chris: Forcing you to dogfood yourself is a good move.
Tom: Right. Yeah, but even beyond that you could say -- at some point someone is going to say, "Redwood is great. I don't want to use React. I want to use Vue." Eventually, we'll have some way for you to write your Web side with Vue. It's inevitable. It'll happen eventually. It's not our focus at the moment, but I think because it's GraphQL, because there is this decoupling, we could build something out that still maintains the Redwood philosophy but just does it in a way that you would approach it from Vue.
Chris: Yeah, that's great.
[Banjo music starts]
Chris: This episode of ShopTalk Show is brought to you in part by Bugherd. Welcome, Bugherd. Glad to have you.
This is a great product. It's spelled just how you think it's going to be, B-U-G-H-E-R-D, bugherd.com. It's about collecting visual feedback for websites. Whatever you're picturing, I think it's better than that. it's really good.
It's a Sass app, so it's a website that tracks all this stuff. It's a plugin for your browser so, if you're like me, and this happens to me all the time, I almost need to not visit my own websites after a certain hour of the day because every second I'm looking at my own website, I'm critiquing everything, just in my mind. I'm like, "Ew, that's not quite right," or I'll find a bug or something because I'm often the biggest user of my own websites.
I think Bugherd taps into that in a way where, if I see something that's wrong, I don't have to then take a screenshot of it and maybe annotate it and email it to myself like, "Deal with this in the morning," or log it somewhere weird. It makes that natural. It's like, "Oh, I see. The Bugherd tool is right here."
Essentially, what they say is it's like putting a sticky note on a website, but you do it right there and then it's not just a screenshot and an email. It's part of the Bugherd system. It's like, "Oh, this is a thing that needs to be addressed." I pointed right to it on a real website and it's attached there.
It's not just you, although you could use it by yourself. I think that would be a nice use case, actually. A lot of us work on one-person teams, but it's team-based, generally. Then everybody can see it. "Oh, look. There's an issue on this website," and everybody is using it all the time. It's not just part of one little sprint. At least that's how I picture this being used and how I'm using it is all the time.
It's just part of the workflow and the workflow is customizable too. It's like, what do you want to happen when one of those new sticky notes happens? It's like, well, it's kind of treated like a bug. It's like this needs to be triaged. The right people need to see it. It needs to be addressed or explained and closed in some way.
It doesn't enforce one particular workflow of that. That workflow is customizable, too, and I think that's a big deal. It works into however you'd normally deal with this. It makes what you normally do better, so check out bugherd.com.
[Banjo music stops]
Chris: We talked opinions to some degree. There are a couple of friends here in the gaps that I'm curious about. It just says GraphQL. Is Apollo part of the party or did you find that you didn't need it?
Tom: Yeah, right now we're using Apollo.
Chris: Yeah, it's Apollo.
Tom: I mean it brings a lot to the table. It brings a lot more than we use currently but, as we gain sophistication, there are a lot of things in Apollo that we will use. My only concern with Apollo is the size of it. The bundle size is still quite large.
Chris: Right. Right.
Dave: I found that you don't write straight Apollo, just in the demo that I built. It was just an abstractive way. You just do db.--
Tom: Yeah, it is. You don't even necessarily need to know that you're writing Apollo. Although, if you get into some of the caching types of things, you'll be using Apollo directly on the--
Chris: Yeah, that's kind of … right?
Tom: --on the client-side.
Tom: Using Apollo client. On the server-side, you really don't need to know because we abstract it away. This is the idea of breaking your back-end logic into services and automatically connecting your GraphQL API to those services without having to write a single line of boilerplate. I'll explain how that works just very briefly.
You start by writing your SDL, your schema definition language for GraphQL. Anyone who is familiar with GraphQL knows that's the primary way that you specify your API. Then normally, with Apollo, you would write a resolver map and you would say, "These functions execute when these API endpoints are called." Then you'd hook whatever those would call, other functions, into your codebase.
The problem with that is that you start to feel like you need to put a lot of code in there. The resolver map itself is not really a very pretty thing to look at. If you have a large schema, you're shoving too much code into that file.
At that point, you start feeling like you want to refactor the business logic out of the resolver file. Then you just call into these other functions from there. All the implementation is somewhere else. Then each resolver is one line of code. But at that point, why not just do that for you? Why not just make that mapping for you?
The other problem with the resolver map is that it's not reusable. If you have an implementation of, like, "Get me a list of my posts," and you want to call that from some other part of your backend, then you're not going to call directly into your resolver map. What does that even mean? How would you do that? You'd have to give it the things that it wants like the context and the parent. What is it? I don't know. The root? It doesn't make any sense, right? It's the wrong way to enter it.
The way we do that is by providing those things as globals, essentially. You're within a single call there, and so you can do that safely. You just have access to those. Any of those functions can call in. They can just use context.
The reason that you want to do that in Redwood is so that your services files now represent your API for a given service to the rest of your backend. Now, different backend services -- and when I say service, I mean kind of a chunk of your backend. These are not independently running things. They're just an organizational structure within your backend code.
If you think about them as discrete services, then you start, again, thinking in terms of APIs. This all comes into how we write long-term, maintainable code. To me, the way that you do that is by thinking in APIs, everywhere. Everywhere that you can, you're defining APIs and encapsulating responsibilities.
Now you have services being able to call into these service files. You just import it and you use one of the exported functions from a service and you go on your merry way. They work as you expect.
At the same time, your GraphQL API is just forwarding requests that come in via GraphQL automatically to the functions that are exported from your services file based on them having the same name as the GraphQL call. You eliminate boilerplate and you make possible reusability and encapsulation across your back-end services.
Dave: My services file for posts has five functions. Posts, which gets all the posts, finds as many posts. Post singular, which takes an ID, just finds one post, create post, update post, delete post. It's very much -- I don't know. I'm reminded of Rails controllers, kind of, here.
Dave: Which was decent technology. It's like, no, all your actions sit in this file and this is the thing that you hit. This is what you interact with.
Tom: Yeah, you could think of them a little bit like controllers in that they're sort of the arbiter of business logic but they're different in that we intend services to be a logical grouping of functionality for your back-end. Let's say you're building a forum software or something and you need billing. It has billing. It has the posts that people make. You might have, initially, in the very basic version, you have a content service that handles posts and maybe the comments on the posts and categories and topics. That might be represented by four or five, six different database tables. A service is intended to cover multiple database tables. It's not a one-to-one mapping.
Dave: Like a controller would have been. Yeah.
Tom: None of this stuff makes sense in the Redwood architecture. A service is a higher level of abstraction than a database table. You want that because, if you start thinking about your backend in terms of just models, you'll end up with everything being tightly connected and you don't want that. Long-term maintainability suffers. You start using everything from everywhere and you get into trouble. I've seen it over and over. I still see it.
A lot of Redwood is just trying to build a system that keeps me out of trouble, honestly, by enforcing these ideas where it's like, "Just do it in this way and you will automatically get better maintainability because the structure of the application itself is guiding you towards better architectural decisions around your own code."
Chris: What about public APIs? Does it guide you towards that too if you're like, "We use these APIs and why not just open them up? It's the same place"?
Tom: Well, that's the beauty of thinking of an API as a service that you're providing to a front-end client. If your GraphQL API is good enough for your own clients, then it's probably good enough for other people's clients. You may need to add some additional endpoints depending on what other use cases people have but, there again, they're just clients.
You can say, "Our front-end client needs these services." Third parties have told us they need to do these other things that we don't currently do. We can build out custom functionality for them that we have no front-end for and there's nothing in Redwood that says that you can't build out GraphQL APIs that you never consume yourself. Again, they're just SDL files that then direct GraphQL calls to services files. It's all the same stuff, and those services files could call into your other servicers files and reuse logic in those.
Tom: Again, to me, you can, and many people are, trying to erase the protocol between the front-end and the back-end. I think you can make that transparent. You can do RPC calls using any protocol you want and make it disappear. To me, I'm at a stage in my life where I want to think rigorously about my application. I want to know that it is going to take me as far as I need to go. APIs are, to me, the best thing that we have from a long-term maintainability perspective.
You can see it. You can see it in any company. Velocity starts to suffer if you're not thinking in these ways. Encapsulation APIs, that is how you maintain velocity in a company. We're really designing Redwood to be a sophisticated solution for startups and companies that are building for the long-term.
Dave: I've heard similar things about, like, Amazon's work, like maybe their greatest sort of invention, I guess, early on was just Bezos saying, "You need to have an API or else it doesn't ship," or whatever. That's how you get AWS. That's how you get all these other things. It just snowballed from that core decision, right?
Tom: Yeah, this is how you get teams to work together and not slow down because you can specify that interface. It's also great from a testing perspective. Now you've decoupled your front-end and your back-end.
You can mock out the database call. Any of the data fetching that you just return, you can mock out the return from a GraphQL call and not worry at all about database tables and where they came from. Separate those concerns on your database side in your business logic. Now you can just worry about, is my business logic side returning the data that is correct from the database?
Chris: Oh, you said it, man.
Chris: I've been trying to say that for a hot minute.
[Banjo music starts]
Chris: This episode of ShopTalk Show is brought to you in part by Netlify. Netlify is the best! If you don't know them, you've got to go check out netlify.com. They're really changing the game of deployment and hosting and that whole world of, like, what do I do with my code? Where do I put it so that it's live to the world? They're really flipping that world on its head in the best way, and not because it's this brand new software that's just complicated but, if you take advantage of it, it's great. No, it's almost changing it because it's so simple.
You make a website. It's probably in a Git repository. When you push to it, you hook up Netlify to that, it sees that code, and deploys it. That makes it super perfect for things like static site generators. If you've built a site with, like, Jekyll, 11ty, Hugo, Metal Smith, or any one of those and there are dozens if not hundreds of them, they're all kind of awesome in their own way. Those types of sites are perfect to put on Netlify because they're statically generated and that's kind of what Netlify is, is a static file host.
But that's that moment in which you've got to be like, "No, wait. This JAMstack paradigm goes so far beyond that." Our guest today, Tom, knows all about that, so I hope you're enjoying the episode today. This RedwoodJS stretches those limits of what's possible to do in a JAMstack environment.
It assumes that you're going to have a database and that things are going to be powered by data that way. Not necessarily everything is prerendered by as much could be prerendered as possible. Expand your mind of what the JAMstack can be. There's still auth. There's still form handling. There's still running of any code on a server you want because of cloud functions.
[Banjo music stops]
Chris: How about just some little baby stuff about front-end just to fill in these gaps because I think it might be interesting to people to understand Redwood. Does it assume -- I assume it does, but I really don't know, like SPA assumes routing?
Tom: Yes. Yes.
Chris: Do you have a baked-in React router?
Tom: We've written our own router.
Chris: You wrote your own router? Ooh!!
Tom: We're a little crazy. Well, it's because we--
Dave: Bold choice. Bold choice. [Laughter]
Tom: It's because we wanted control and we wanted our router to look a certain way and have certain constraints that React router wasn't doing. It's possible that someday we could. I don't know. I needed it to look a certain way. I don't like to be constrained by tools.
Tom: If the tool doesn't feel right then I'm going to do my own and we'll make it sophisticated. It'll take a little time but we'll do it. Then once it's done, you're going to love it. You're going to be like, "Wow, this is exactly what I've always wanted."
Chris: That's great.
Tom: It's a SPA. We're not focusing on SSR, server-side rendering. That's not our primary focus. I'm looking towards the future, and so this is -- again, Redwood to me is -- we're almost building it for a year from now, for like targeting.
This is like a game developer. When you build a game, you can't develop it for today's graphics cards. You have to build it for the graphics cards that will exist when you launch.
A lot of the gamble of Redwood is, if we start building now and we make these assumptions about what technologies will be available and competent a year from now when we intend to do a 1.0 release, then we can hit that target more effectively than other people who are building for today's targets.
Chris: Wow. That's pretty bold because it says right in the title, "Bring full-stack to the JAMstack," which to me a little bit implies SSR.
Chris: But not to you?
Tom: Well, so we're stretching the meaning of the JAMstack a little bit, intentionally.
Chris: Yeah, sure.
Tom: The JAMstack is this broader philosophy that involves a Git-based deployment that's sort of built into the idea of JAMstack.
Tom: And your deploy target and how you distribute. You put it on a CDN.
Tom: This gets into some of the edge--
Chris: The functions all work and all that.
Tom: The edge, yeah, so it gets into the edge readiness of Redwood but you can already see with places like Netlify and Vercel that they've embraced Lambda functions as a place where you can do logic. This is why Redwood exists at all. When Netlify came out with their functions and I saw them, it kind of blew my mind. To think that you could just put code in your Git repository and deploy it, Netlify would pick it up, put your HTML, CSS, all your front-end stuff on CDN, and then deploy your functions to Lambda without you doing anything.
Tom: To me, as soon as I saw that, I was like, you could do normal, like, full-stack websites on this. We have to do this now. This is critical. It's critical that we do this.
And so, I almost immediately started thinking about it and working on it. I started working on Redwood a year and a half ago, almost. But again, it's like these ideas were kind of half-baked and the technology is wort of weird. Do all the pieces fit together?
Tom: None of them were really quite where they needed to be but, as we've been working on it, they've gotten there. Now, the biggest unsolved part is the database.
Chris: Quick, on those function things, you use them, right? The idea that there's this folder full of functions and they all run little Lambdas and stuff, which I agree is just the coolest thing ever. Does that mean you have to deploy it to Netlify?
Tom: No, there's nothing about Redwood that would be specific to Netlify. It's our first target because this is my dream. The reason that that's the case.
My dream of a future is for something that, in my mind, call a universal deployment machine, which means I write my code. It's all text. I just write text. Then I commit to GitHub. Then it's picked up and it's deployed into reality. That's it. That's the whole thing. That's what I want. That's what I've been looking for.
Chris: A service figures out what you need from that text?
Chris: It's like, I see what they're trying to do here. It's a database.
Tom: Yeah. Right. It's some configuration as code or it's just implied. Then it's done. I don't worry about physical machines. I don't have to care about where they are if I don't care right now. I don't have to care about how much RAM they have. I don't have to care about where compute occurs - all of that.
It's like what Heroku to me could have become eventually but they are not and they have not. For the past eight years, I'd say, at least, I've been looking for a company to invest in that is essentially this universal deployment machine. It's just something that I thought was possible. Just abstract deployment to such a level as you just don't care.
I don't know that this is a novel thought. I think there are probably many people that have had this thought where it's just like, "Why do I have to care about servers anymore?" Serverless is a push to that but it's not even far enough. You need something like this JAMstack mentality that you could use Git that you just commit to a Git repository, push it somewhere, and boom, the rest happens. Netlify is the closest thing that I've seen to a universal deployment machine.
Tom: That's why I got involved four or five years ago. When I saw it, I was like, "This is awesome and it could be even more awesome." Then it just kind of grew in that direction more and more. As I've been involved, I've tried to kind of mold it that way as much as I can as an outsider. Although, I am on the Netlify board now.
Dave: Hey, now you can--
Chris: That's nice.
Tom: I can get my fingers in the--
Chris: You seem to make smart calls.
Dave: Put your thumb on Matt. Yeah.
Tom: I'm super excited about where we're going now. I think Redwood can be a driver of the imagination of, like, what can you do in the JAMstack mentality? The reason that we're focusing on Netlify is because it's my dream of a universal deployment machine. But the technology is not ready to do that completely, right? Lambda functions have restrictions. If you put a GraphQL API on a Lambda function, eventually that won't work because you'll need to split it up. We can find ways to do that, split up your API across multiple Lambdas so that you don't run out of the storage.
Dave: Three hundred whatever meg limit or whatever.
Tom: Right, whatever the limits are, but then you see the limits going up every year.
Tom: Lambda continues to march its increase towards awesomeness and it will continue to do so. Imagine what it will be like in one, two, three, or five years.
Tom: But for now--
Chris: Lambdas are a good bet. That's for sure.
Tom: Yeah. It's inevitable that they'll get there and I would love for Redwood to be a reason that they get there faster. If they start saying, "Hey, look at all these people building full-stack applications using Lambda with these frameworks and distributing their front-ends on CDN and talking via GraphQL," we need Lambda to work better for this specific use case. Then they're like, "Let's build some stuff to make it work better for that use case." That's my dream.
All that said, there is no reason that you couldn't spin up your own servers and deploy a Redwood application to a normal thing that is running your GraphQL API that has as much disk space as you want and has as much RAM as you want.
Tom: And is in whatever locations that you want. There is no reason at all why you couldn't do that. Now, we haven't built a deployment machinery to do that but we will. It's in construction as we speak.
Tom: But again, my dream, my vision is for this other thing but Redwood will work just as well as anything right now in a more traditional deployment structure.
Chris: Do I detect a bold bet there, though, that SSR is a year or maybe it takes longer? Is there any point that pre-rendered stuff just becomes less of a big deal?
Tom: What I hear mostly is that people want SSR because they're afraid of SEO hits. That's one of the primary driving factors. There's also performance implications.
You can get, I think, most of the way from a performance perspective and an SEO perspective with pre-render. One thing that is not built into Redwood yet but will be is the idea that you can pre-render based on a route. Let's say you have some marketing pages, as many websites do. They aren't really using much from the database or those things could be pulled in after the fact. Those can be built and deployed in a traditional JAMstack-y way where you pre-render a build time.
You pre-render all the way to HTML. You don't need any React or anything if you didn't want to. Certainly, you could have React in there if you'd like. There could be different levels of how much -- how far you want to go from a pre-render perspective. You could go all the way to just HTML, CSS, and then you choose what pages you want to do that.
By route, you just say, "Pre-render this route. Pre-render this route. This route takes parameters, so here's a list of the parameters that this route can take and just iterate through them at build time and build them."
None of this is revolutionary. People already do this. But it means that, from an SSR perspective, you can solve some of those problems.
The problem with SSR is that it's just a pain in the ass. If we can drop it, then we should, and so it's not my focus. People want SSR right now. That's fine.
Tom: I think we can get most of the way there and solve most of people's use cases without SSR.
Chris: Yeah. I guess I mostly meant pre-rendering, like having HTML files that represent a route.
Tom: Oh, yeah. Absolutely.
Tom: Pre-rendering, that is the mainstay of the JAMstack.
Chris: Yeah, right. Right.
Tom: That is today what most people think of when you say JAMstack is pre-render. Absolutely, we can do that. Why not? Of course. Build time. I mean we have a build step. If you have a build step, you can do all kinds of things with a build step.
Chris: [Laughter] You can, indeed. Yeah. Yeah, I just didn't know if there's even, like, what's one step beyond even pre-rendering? Is it edge, like just this URL is already ready to go?
Tom: Yeah, well, there's now incremental building that you see becoming available.
Tom: Like Gatsby.
Chris: That seems like a big deal.
Tom: You can do incremental builds in Gatsby. That's necessary for large content sites because you just don't want to sit there and pre-render build all your pages every time. If you make one change in your CMS and you're like, "Okay, let's rebuild everything," or even just rebuild that one page and push it out, if you've got some local caching or whatever, like, it's all painful. If you can have your host do that for you and just push out your change and then someone pulls a page that's not in the cache or you could warm the cache if you have some cache warming process. But for smaller sites or whatever, let uses kick off the first render of it. Store those in the cache. The host can do that for you, and so I think we're getting a lot more sophistication around how big of content sites you can serve with the JAMstack mentality and not suffer these really long build times. We can leverage the same technology.
Chris: Yeah. I wonder. It could almost become the job of a good host to do that pre-rendering rather than the framework that you happen to pick.
Tom: Yeah. I mean you could just send it a list. Let's say you're not doing pre-rendering at your build time. You want to do it sort of lazily afterward where it's like, "Okay. My site is out there." The deploy was essentially instant. Then you have a file that is just iterated all your possible URL parameters or whatever.
Then the host is like, "All right. I'm just going to hit all these and warm the cache." It takes a few seconds, a minute, or five minutes, or whatever it takes. You don't care.
If someone gets one of the pages in the meantime, it takes them a second to get that page but it's not the end of the world. Then from then on, after the cache is warmed, it's like 30 milliseconds to get that page and you're very, very happy about that.
This is universal deployment machine stuff. This is exactly why I love Netlify because Netlify is doing all of this stuff.
Chris: It is notable that they don't even touch data yet, unless you're talking about--whatever--some data that's just in your repo.
Chris: You're choosing some flat-file storage or whatever. That seems interesting. So far, it was like they seem to have partnered with Fauna to some degree.
Chris: like, "Use that. That's a good one.
Chris: It's like it's been an obvious next step for a while, but I don't know because sometimes companies and frameworks choose a wider funnel on purpose. If you look at Gatsby, their bet is, "I don't want to be your datastore. I'm going to build connectors to every goddam datastore there is. That's our bet." Whereas I don't know. It's yet to be seen what Netlify's bet is. Is it going to be, "Use our datastore," or is it going to be, we'll help you with whatever datastore?00:45:58
Tom: Yeah, the data part is hard because this is still where differentiation in the needs of applications is greatest is in what database technology you need. People are going to want certain characteristics from their data, from their database, and so you might choose a relational database or a no-SQL database. You might need different levels of global distribution. You want a certain number of read replicates. You want your write data to have certain performance characteristics that allow you to do certain geographic distribution or not.
Chris: Is this part of your dream in some way that you don't have to--? Even this -- is this -- will some developers always have to care about that kind of stuff or would even that become--?
Tom: I don't think it's a forever need, no. I mean, yes, this is part of the dream of a universal deployment machine would be that you wouldn't have to care. Now, we're farther off from that because data is data.
Chris: Yeah, it's weird.
Tom: It is where everything comes from and how you query it can be so drastically different. Just running CPU cycles is running CPU cycles, business logic, right? Serving static HTML out of a CDN is that. These things are -- in this architecture, those things are more generic. It's just serving files and churning a CPU.
The data, it really is dependent on your specific use case. That's why it's the last to fall.
Tom: I think it can fall and I think you could even have a system, eventually, that could be a unified relational and nonrelational database where you get the performance characteristics of either, depending on what you want. Let's say you have your relational data talking about like a forum or something. You can have all your relationships between your posts and who makes comments on what and who is following whom. But then, at the end of the day, each post is just a blob and you don't need a relational database for that. You could look that up from anywhere.
Tom: You could edge cache it.
Chris: I don't know how to make good decisions like that. I've been a front-end developer forever and I care about data. I know it's important, but I don't know how to make smart decisions about data. I could do real simple stuff.
I think Firebase was a big, early player in this. It's just like, I don't know. It's like a big piece of JSON. Good luck. You know?
Chris: That was appealing to me, so I'm like, "Oh, I know how--"
Dave: You can't have arrays. Sorry. [Laughter] We'll get back to that later. Yeah, everything is an array. You can't do an object.
Chris: There's a lot of money in it, right? Amazon would want nothing more than for you to use Aurora because, once you have, you're there. You know?
Tom: Yeah, yeah. Yeah, so I just think it'll take some time to get there on the database side and people are doing interesting things. FONA is doing really interesting things. I think the developer experience of FONA needs to be refined because it brings in new concepts and no SQL. You can do relation-y kinds of things but it's a whole new technology you have to learn.
Can we do something that looks more like relational or can FONA be that? Can Mongo be that? There are a lot of people working on databases. I think it's an unsolved problem, still.
I think at some point the machine could be intelligent enough to just do it for you. Machines are good at figuring out how to optimize things. We can teach them to do that where you're like, "I don't care where you put my blog of this post. Just make it fast for me to access."
It could be like, "Oh, I've analyzed your usage patterns and so I'm going to move the storage of this post data into this other sort of database situation so that it can be accessed more quickly," and it just does it for you. I think that's totally possible.
Chris: What does Prisma assume? I don't know much about it. You're not betting on it, like it can go anywhere, right? Prisma can connect to MySQL but other stuff too?00:49:35
Tom: Yeah, so it's Prisma 2. Let's differentiate from Prisma 1, which is more tied to specific technologies and GraphQL and stuff. Prisma 2 is really more of a query builder. It's like, "Here's code. Generate the correct queries to query a specific database."
Right now, it supports MySQL and Postgres. In the future, it will support FONA, Mongo, and all the no-SQL databases. They intend to be sort of a unified interface to your data, whatever that means. That's neat because, with an architecture like Redwood, it will be easy to say, "I want either a relational database only or a non-relational, a no-SQL database only, or maybe I want a mix." You've got to figure that out for yourself right now where you're going to store which things, but there's no reason that you can't have connections to both.
If you properly serverless style databases like FONA where it's built from the beginning to work in a serverless fashion where you connect from anywhere - it doesn't care. You connect as many times as you want. It doesn't care. It takes care of all the connection pooling issues for you or whatever.
I think, more and more, it will become that way as more people build with this architecture, which I think they will because it's powerful. You can scale infinitely with this architecture with no effort for certain websites, but it's probably like 80% to 90% of most Web applications.
Chris: Yeah. Just like JAMstack, right? It can be about 80%, 90%, let's say.
Tom: Right. It covers most of the use cases and I think that's great. For those last 20%, you're going to be writing some custom stuff anyway.
Chris: Prisma is opinionated a little bit, but it's your way of being opinionated without being opinionated.
Tom: Yeah, well, we chose it because we come from the Rails world and we need something that looks like active record.
Then we came across Prisma, which was very early. This is probably almost a year ago now - super-duper early. We're like, this has the beginnings of what feels right. We're like, "All right, well, let's switch to this and see how it feels," and so we implemented it with that.
We've worked closely with them ever since. We know the CEO over there very well. We talk. He comes and we talk with him fairly often. I do a lot of work in Berlin anyway. They're based in Berlin, so I go over to their office sometimes.
We have a really close relationship with Prisma, which is great because it means that we can influence some of the direction. We can say, "Redwood needs this from Prisma," and they'd be like, "Oh, okay. Great." They love Redwood because it's a reason why people would use Prisma. It's a win/win for both sides. We can both help each other be better. It's just really nice.
It doesn't do too much for you. It's not trying to be an ORM. It's not trying to create these really fancy objects out the other end.
Rails has models and the problem with models is that models turn into these giant bags of functions and grow unbounded forever because your code has got to go somewhere, right? But then you've got all your code attached to your database tables, which is not how you should think about things. I think it leads to too much table bound code anyway.
In that, you then start thinking in a more functional way and saying, "Where should my manipulation of this data exist? Indeed, which service should own the manipulation of that data and be in charge of writing that data back to the database?" That again gets back to long-term maintainability. Where does your code need to live? How do you want to split up your services? What's the right APIs? Not having too much power and encapsulation of this bag of methods around manipulating objects, I think, gets you closer to this more functional mindset that I think just leads to be better, long-term maintainability.
Tom: Yeah, right. Yes, and also migrations are like, "Oh, yeah. We have query builder and also migrations." We're like, "Migrations! Hello! Yeah. Where have those been?"
Dave: Oh, yeah.
Tom: Right? We need those.
Tom: If we want to be anything that someone coming from the Rails world is like, "I could actually use Redwood." Then we're like, "Okay. Here's how you do migrations." They're just like, "I’m sorry. I have to do this all manually. What's the process for this?" It wouldn't be enough, right?
Prisma being like, "Oh, we got migrations too," and indeed, their migrations are declarative, essentially.
Tom: It's like, "Here's what I want my database to look like. Figure it out."
Tom: This is more towards the universal deployment machine idea. It's like, just tell me stuff and I will make it happen for you. I'm a machine. That's what I do. That's what I'm good at.
Dave: Yeah. No, it's very cool.
Dave: Just to tell you where I'm at in the world, I wrote my first Rails migration this morning.
Tom: Oh, congratulations.
Dave: Oh, boy.
Chris: Thanks. Thank you. I'm really a front-end guy. I'm not like a baby developer, but I really needed to change a fricken' table for something I was doing and I didn't want to wait to pull somebody aside to do it, so I read the flippin' manual and I did bin Rails, whatever, and scaffolded the damn thing and wrote it. I was very proud of myself. [Laughter]
Tom: Good work.
Chris: Have you seen--? This is just as interesting because there's a little bit of magic. We only have so much time and I'd like to at least get you to mention your startup and what's going on there. There's this thing called GQL lists I saw the other day that looked cool as hell. If I was making a framework, I would bake this in, in a minute, because frameworks have magic in them, you know. Why not have more magic?
Chris: It's like you don't even write the GraphQL query. You just use stuff in the component and it just knows what you mean and it makes the query for you. I was like, "Oh, that's sick."00:55:59
Tom: We've looked at that. It's cool. It's a cool demo. It makes me afraid because--
Chris: [Laughter] It should make you afraid.
Tom: [Laughter] It makes me afraid because, to me, it's too much magic. There's too much that it's doing that I don't understand. Also, one of my gripes about Rails is that it can be too magical sometimes. It's like, "How are you doing that? Tell me how you're doing that."
Chris: I got it. I had a thing where I type debugger. I ran the code in Rails today and then it gave me this object. I was like, okay, object. It showed me what was in it and I was like, the thing that I'm looking for isn't in it. But then, in the console, I typed thing.thing and it was and I was like, "Oh, it's a method on itself but it's not in itself and there's no way to determine if a thing dot something, if that dot is inside of it or a method on it. There's no way to distinguish that.
Chris: It blew my mind.
Tom: Yeah, and so this comes to Ruby. Again, I love Ruby. Ruby made me what I am, but I have my gripes with Ruby as well.
Chris: Well, I hope that's one of them.
To me, it encourages you to do a bit less of it. The magic elements in Redwood are pretty thin. They might feel kind of magical like just automatically connecting your services files to your GraphQL SDL definitions and creating the resolver for you. But if you look at the code that does it, it's like 20 lines, or the idea of cells which allow you to do more declarative data fetching in your front-end. It's just a higher-order component that's like 30 lines long.
The magic, we want it to feel powerful but you can understand it. You could go in and read the code. If you want to understand the Rails magic, sit down because you're reading 50,000, 100,000 lines of code.
Dave: Yeah. You get dumped into. It's like api.rails or whatever or the guide, which is 200,000 words.
Tom: I like a little less magic because, for me, it's always about long-term maintainability. I will gladly take a little less magic if it allows me to trace what's going on. In a mature Rails app, even tracking down what code runs on a certain page, you're looking at an element on a rendered page and you're like, "Okay. I'm going to trace down what code is producing this rendered output." If you're using React, it's better because you can get the inspector and you can find out exactly what component it is and componentization is great. That's why React is awesome.
In the traditional way, you're doing it and it's so hard. It's so hard to track down.
The way that I do it is, I find a string that looks fairly unique around that thing that's being rendered and I search for it. That's just not--
Chris: Oh, yeah. Amen.
Tom: That's just not a good answer to that problem, right?
Tom: React and less magic and GraphQL, and all these things that allow you to just trace what's going on. That kind of transparency into how the front-end talks to the back-end, it's logable. You can log those GraphQL queries and see all the data that's going through in a first-class way instead of trying to tease them out of some Restful call and it's all jammed into the URL or it's in a post somewhere.
GraphQL, I also have my problems with GraphQL. I have my problems with everything. Don't get me wrong. GraphQL has its issues. GraphQL's main problem is that it's young and it doesn't have enough tooling around it yet. But we're doing things to solve that.
One problem with GraphQL in the back-end, for instance, is that you have the N+1 problem. Right? You're like, I want to get this record and all of its children.
Tom: Which you could do in a single query and it would come back very fast. But the way that GraphQL splits things up, you get the object and then you run a resolver once for every single thing that comes back. That's the naive implementation that a GraphQL API would give you. You get N. Well, it's 1+N. You get one, the parent object, and then the resolver is going to run for every child in the result set of that, like, "Give me my list of posts."
Now I'm going to go fetch every post individually, right? That's insane. You would never do that.
Solving the N+1 problem is something that we're going to have the machines do for you. In fact, Prisma takes care of this for you, so you do batching.
Chris: Hmm. I was curious. Apollo solves some problems in that arena too, doesn't it?
Tom: They might, in some of their technology. Not the stuff that we're using currently, but I don't actually know. But this is a problem that machines should solve for you.
Tom: You're like, I'm going to write this in the way that GraphQL wants me to write it with resolvers where the resolvers are very easy to write because each resolver is just saying, "Just fetch the data that I'm responsible for and then delegate any sub-objects to their own resolvers." It's a beautiful, easy to understand way to fetch data but it has the performance characteristics that are challenging. Our solution to this class of problem always is, teach the machines how to do it for you.
Chris: I had a GraphQL error today that, from a developer perspective, the debugging, what it threw to the console was inscrutable. It was absolutely useless. I think of that as it being young, too, is that the developer experience sometimes is just hopeless. You have nowhere to go.
Tom: Yeah. This is another challenge that we have, which is making error messages consistent and reasonable across all of these different pieces, which is something that I can't say we solve particularly well yet but it's absolutely something that we want to solve.
Chris: Great. Well, we just have a few minutes left. Chatterbug, do you want to pitch it, tell us about it, and anything?01:02:08
Tom: Sure. Yeah, so my current startup is called Chatterbug. It's for foreign language learning. If you want to learn Spanish, French, German, or English, then you can do that. We're focusing on the German market right now. We have a lot of steam over there and so we're kind of doubling down in that market.
Tom: It's been really fun. I co-founded it with some other GitHub, former GitHub people. One of my co-founders is Scott Chacon and then Liz Clinkenbeard and Russell Belfer also were GitHub team members, and so the four of us got together and we started this language learning company. It's just continues to be an unsolved problem in the world. How do you learn a language most efficiently and most enjoyably?
Tom: Can you apply software to that problem in a tasteful way that nobody has done before? The market is large and it's challenging - let me tell you. [Laughter] A totally different animal from GitHub, a completely different market - consumer.
Tom: It's definitely stretching the boundaries of my imagination and problem-solving.
Dave: As somebody who went to school for Japanese, lived in Japan, and then left Japan and I've been losing Japanese rapidly ever since, yeah, you're just like, "I'd pay somebody $20 a week or month or whatever to just practice. That would be great."
Tom: Yeah, so I'll give you a quick taste of the magic of Chatterbug. That is, it's kind of a two-part system. The main thing that we offer are what we call live lessons, which are 45-minute chats with fluent speakers. This is because, if you want to learn a language, it turns out that you get good at what you practice. If you practice speaking, you will become good at speaking. If you only practice doing dual lingo on your phone, then you will get very good at dual lingo on your phone.
Tom: If you go to a country--
Dave: Me and the owl are pretty tight. [Laughter]
Tom: I mean it can be an element of learning a language but it by no means will help you speak that language, right?
Tom: If you spend a year, every day, on one of these digital-only apps where you're doing vocabulary and a little bit of, like, speaking into the microphone and you go to the country where you want to go and speak that language, you will be useless - guaranteed; 100% guaranteed you won't be able to say anything useful.
I don't know why this is shocking to people because, everywhere else in our lives, we're like, "I'm going to read books about--I don't know--baseball and then I'm going to go play baseball and I expect to be good at baseball." You're like, no. Nobody expects that. Everybody knows that you can't learn to play baseball from a book, but everyone expects that you can learn a language from a book and it's just not possible.
You can learn about the language. You can learn a lot of things that you need to speak the language but you will never actually be good at speaking the language because your brain has to adapt to speaking the language. We have built a system around building an entire curriculum that you can go through, as you study on your own. The most efficient way to learn vocabulary is through spaced repetition, so we have spaced repetition. We have readings. We have videos and audio that you can listen to all on your own.
Chris: But then you practice for real.
Tom: Then you go and you practice it. You put that stuff into use and the system keeps track of where you are and makes sure that the material that you're going over, the exercises that you go over in the live lesson with your tutor are exactly what you've been studying and so your mind is optimally prepared for that experience. Then you kind of go back and forth between these two things. Do some study on your own. Go talk to a fluent speaker. If you do that, you can learn a language.
If you were to do it -- this is the other thing with language learning. Everyone is like, "Oh, learn a language in three months. Become fluent in two weeks." I don't know what they say these days but it's all BS.
Tom: You need at least--
Tom: If you were learning full-time, like eight hours a day five days a week, you could become -- I think the data that we have suggests that you could become -- you could have a decent level of conversational fluency in one year.
Chris: A year!
Chris: Holy crap.
Tom: Yeah, well, this is the problem with the industry is that the industry makes you feel like you can do it substantially faster than that.
Tom: It's almost impossible. You might have a savant here and there that can pick up a language in a much more rapid way, but normal people with normal amounts of time, it takes a long time but it's as skill that you pick up when you do. Our job is to not lie to you that you can do this without practicing it and then make it possible to put the time in and stick to it.
Chris: That's great. I have this thought about practicing where the amount of time that you can practice expands as you get better at it. Like if you said you want to learn the piano, practice eight hours today, you can't. You literally can't practice eight hours that day because you just don't--
Tom: Right. It's exhausting.
Chris: You're going to be tired. You just can't do it.
Tom: It's exhausting.
Chris: But if you're a great piano player, you could play 16 hours a day because you just -- the world is open to you.
Tom: Right. A lot of it is the mental overhead of it.
Tom: Once you build these things into muscle memory or, I guess, tongue and larynx memory when it comes to speech, I guess--
Tom: I don't know if those are muscles.
Tom: Then it takes less mental effort. This is true across any skill that you want to learn. Take any skill you want to learn. You can learn literally any skill you want. All you have to do is put the right amount of time in. It doesn't matter if you're a kid or you're an adult. It's just a factor of time. There might be some slight differences in the slope of the learning curve between the two but the primary factor is time. Dedicated, deliberate practice is the thing that matters most.
Chris: I'm amazing at watching TV, for example.
Tom: You're really good at watching TV, right?
Chris: Yeah. [Laughter]
Tom: You're like Malcolm Gladwell level expert.
Chris: [Laughter] I am.
Tom: TV watcher.
Chris: It's amazing.
Dave: Remote with your eyes closed. It's incredible. It's just incredible.
Dave: Well, we should wrap up. Thanks so much for coming on, Tom. We didn't even talk about my favorite feature, cells, of Redwood that much, but it's a wonderful innovation in front-end.
Tom: Yeah, you can go look at them on the website. The website is redwoodjs.com. Oh, and I should say, if you want a Redwood sticker, I will ship one to you literally anywhere in the world for free.
Tom: There's a form on the redwoodjs.com website. Go fill it out and I will ship you stickers and you'll have them in a week or two.
Chris: That's great.
Chris: Clear link.
Dave: How can people follow you and give you money, if they're not doing that already?
Tom: [Laughter] You can find me on Twitter, @mojombo. The same on GitHub. Chatterbug is chatterbug.com. I think those are the main ones.
Dave: Awesome. Thank you.
Chris: I can just feel this data from this form going through Prisma. I can just feel it happening.
Dave: Just getting consumed. Thank you so much for coming on the show. We really appreciate that and your time. Thank you, dear listener, for downloading this in your podcatcher of choice. Be sure to star, heart, favorite it up. That's how people find out about the show. Follow us on Twitter, @ShopTalkShow, for tons of tweets a month.
Head over to ShopTalkShow.com/jobs and get a brand new one because people want to hire people like you.
Chris, do you have anything else you'd like to say?
Chris: Hey, ShopTalkShow.com.