skip to content

Build a Reactive Backend for a Web App

Convex is a JS-powered database and backend for web apps that’s reactive — you can use it to create endpoints and queries that update automatically when data changes. Tom Ballinger will teach us how.

Full Transcript

Click to toggle the visibility of the transcript

JASON LENGSTORF: Hello, everyone. And welcome to another episode of Learn With Jason. Today, on the show, we've got Tom Ballinger. How you doing, Tom?

TOM BALLINGER: Pretty good. Yeah, pretty excited about this. I watched some archives, seen some of your show before. Excited about writing some code. I can talk first, but excited about getting this code.

JASON LENGSTORF: I'm really excited about this one. Let's talk about you. So, for folks who aren't familiar with you and your work, do you want to give us a little bit of a background?

TOM BALLINGER: Sure, sure. Yeah. It's a little similar to where you were at. At some point  it wasn't PHP, it was Python. It's past� it's postGoogle Maps, JavaScript's out there, you can make dynamic apps out there. Was started to play with that, maybe 12 years ago. I was at the Recurse Center. Let's find a way to work together and� not to work together, just work on stuff instead of having a class. Whatever it was you wanted to learn. In three and a half years I was there, it was very much, like, yeah, JavaScript's a lot of it. I was at Dropbox. We were reporting the desktop client to something like Electron, where the UI's going to be JavaScript. Infrastructurey, but also product.

JASON LENGSTORF: Yeah. Yeah. Yeah. So� so, let's  let's talk a little bit about Convex. Because Convex is a bit perplexing. Anthony in the chat just called it "perplexing." I think I would agree with that sentiment because it seems magical and what I mean by that is, it looked like there wasn't enough setup for what it was capable of doing when I looked at the doc� and I only looked for a minute.
So I'm coming in with two datapoints. One is, I looked at the home page and I was like, dang, that looks like magic. And Convex is part of the Jamstack Innovation Fund, which Netlify put together. There's a big vote of confidence from Matt and Chris, the founders of Netlify. You all are going to be at the Jamstack Conf. Y'all should go to it.
So, yeah. Let's talk a little bit about� maybe just highlevel, elevator pitch. What is Convex?

TOM BALLINGER: Sure. Sure. Yeah. I say, Convex is a hosted backend and database. Back into the service is a thing you might be familiar with. Think Firebase, the idea that a web browser can talk directly to your database somewhere. It can get that information and send it right to the browser. You need more logic where you say, don't just dump this whole database table, I want derived data based on that. We see it as continuing the Reactive, Declarative data flow all the way to the backend. So, when a table changes and then therefore, a API response would have changed, based on that table data changing, I would like my app� all my users apps in the browser, that should update live.
That's the high level. Taking the dataflow you have in React, or a lot of things, and taking it all the way to the database.

Gotchu, gotchu, gotchu. Love the elevator pitch. What I was saw very minimal setup to make this work. What I'm curious about is how did you think about this API when you were designing it. What I saw from it, it was almost like you install this and you just start writing queries. There's not the boilerplate, there's not the elaborate setup.
What was your big inspiration when you� when you were looking at the API for Convex?

TOM BALLINGER: Sure. Yeah. Well, there's a lot to draw from. Right. We are two� our two, big hooks are use query and use mutation. We didn't come up with those names or the idea. A declarative view of your data. If you're using React Query or SWR, it is updating. Maybe it's every time this view loads or maybe it's every 20 seconds in addition to every time that you forround that browser tab. This is that same idea, but the moment it changes, that's when I want that update there. A lot of the things are similar, give me my data here. It is that instant thing. What we're inspired by� I mean, the overall� the system is sort of inspired by having built systems like this, where you end up getting a web socket so you stream things to the browser so, updating live. Building that over and over. Saying, okay, it seems like that is a model we like because I've� when I was at Observable, we built something like this, at Reduct, and Asana. That is a format that works. We've all seen used query, it's a great idea. It's great.

JASON LENGSTORF: Heckin' great.

TOM BALLINGER: You want to bind data to your React components and I think that's the big idea. This thing is everywhere. The simple setup� I'm curious about� it's great to hear that the setup feels simple. Some of that simplicity is that you get to write imperative code, you get to write JavaScript� JavaScript talks the place of SQL and that's one aspect of simplicity. If you want a transaction, I want to do this thing and this thing but if a second thing doesn't work, roll that all back or consistency where this query and that query, I want them at the same time because if I got them at different timestamps and foreign keys wouldn't match up. Those are the pieces that I guess� uniform. When you're using a database� data storage of any kind, these things come up and we say, great, how can we take care of that for you? The simple things seems to be� when I was doing data engineering at Dropbox, I write SQL. You translate complicated logic to SQL so we're saying, let's write that logic in JavaScript. It's still a transaction and that can pop out in your browser.
I'm taking your question of, how do you do it� what is the inspiration of the API and listing simple�

JASON LENGSTORF: But I think that's sort of� this is the part that gets interesting to me. Every tool is built on a world view. If you look at certain tools, they are fullybatteriesincluded. There no configuration, whatsoever, to do. Wix, there is no option to not use the abstraction. That's the goal. You, as the person consuming the tool, don't have to understand how it works to use it. And then there's the other side, which is, you get a collection of utilities and you have to assemble those utilities and need a ton of contextual knowledge. A ton of additional knowledge in order to assemble these tools properly because you don't get instructions for how they fit together. You get, this utility solves this, this utility solves this.
There's a spectrum between those two� I guess there's further where you're literally building those utilities yourself. I tend to think that gets into a bit of absurdity when you're building everything from scratch. You're causing yourself pain. There's a good conversation to be had about where, in the spectrum, does someone sit where they feel is the proper place for a tool to live? In databases, right now, are kind of going through what I would call a full renaissance, where it felt like databases were pretty sleepy technology for a long time. People were innovating, but you would kind of see, okay, you've got a SQL database or a NoSQL, you got to think about how do you scale this, how do you replication, how do you do database sharding, all these things that are very DBA, or database architectfocused, deep expertise. That part, it felt like that was the way things were.
But we've seen this Cambrian explosion. There's all these spots along the spectrum where people are looking at data stores and thinking, it could be better. What I'm interested in the various, different approaches we're seeing from Convex, from Planet Scale, Fauna, where's the right place for data to sit in the toolchain and how much should people know about data to use it?
So what you're talking about, the way you're bringing up this idea that JavaScript is how you write a transaction, that's great because if you tell me to go write a transaction in SQL, I'm hosed. But I can figure out how to do it in JavaScript because I know JavaScript. I think this is the sort of thing that I find really fascinating is how we're bringing these more advanced concepts forward.

TOM BALLINGER: Some of those� sorry to interrupt. The pieces I hear, when you're mentioning, dealing with the sharding and all these complications, that is very much what the founds of Convex were doing at Dropbox, hey, we need to store multibits of data. It does get� you get into they are building their own tools or layers and layers on top of MySQL. It was a focus on, wow, we had to do a lot of work for this. Maybe if you're doing it at Dropbox scale, it has to be done like that. But most of the time, it doesn't. We should be able to design something from the groundup for web development and it should work for that and it's� both the API, I was excited about from having built the web socket service before and let's build this in a architecture that will scale so that nobody else has to worry about this again. Not "nobody," so I don't have to worry about it when I go build an app.

JASON LENGSTORF: That's the piece I find really exciting right now. We're starting to find� especially in WebDev, there were things that we were doing that were very bespoke. When I worked at IBM, we had a whole team dedicated to DevOps and it was a ton of work and it was frustrating for the ops people, the frontend developers waiting for the ops people. You had to deal with inconsistencies because there just was a constraining factor of literal hours in the day that preventing us from getting, you know, unlimited staging in dev environments, from being able to experiment with productionlike environments. A lot of question marks and complexity. And it feels like we've all started to collectively realize, as an industry, that that is a silly thing to do for the web. The web works the same way under the hood. You have to get some static files up to a server, scale those servers globally, make sure the things are up and cacheable and fast and when you get to your services, you know, you need some kind of business logic layer, whether that's serverless, edge functions, a longrunning server and data access.
Each of those pieces is not unique, like, the way that a database functions, in a website, is not unique. I need to be able to read, write, update, delete data. That's it. And when you start looking at those functionalities and you realize that every single company was being forced to employ these ultraspecialized developers to just do the same work that every other team was having to do, it's a huge� it's frustrating. It's a huge waste of time and talent. Those ops engineers should be focused on the uniquelyvaluable thing, is our database optimized? Is the way we're managing this infrastructure or the way our backend services being built and deployed, that's where their talent is needed. Scaling a database, who cares. It's a commodity. And so that's the piece that I love to see the innovation in is we're taking all these chores and turning them into businesses so that developers get to build value instead of doing the yakshaving.
For anything who just heard the term "yakshaving," let me share a quick link to an article about it that I wrote.
So, it sounds like where Convex came from was from that pain being felt at Dropbox and other places around, you know, all� what we just talked about.


JASON LENGSTORF: So how� like, every tool has a sweet spot so when you're thinking about Convex, what is the sweet spot for it?

TOM BALLINGER: Sure. Yeah. Well, right now, it is, hey, I have data, I want to store it somewhere, I want to get it back. That's a cop out because that is the general thing. That is what everybody wants. It is� I'm writing an app and the� like, what appears in� on my web page really is a function of what's in the database and if that's the case, you want this system. Then you need to start layering and other things. Also, I'm a live streaming service and I need servers that do streaming stuff. We don't host streams. There are all these special things. When you spike out and say, actually, my company has basic crud needs and it would be need to build these apps and have live updating and also, we have this other thing which we have to have a whole system� we have some other piece that is different and so there's where you'd pair Convex with something else or maybe you do it all custom or you say, I need deep integration with something really, really different. We've had some teams that use Convex, okay, we've got our existing ML infrastructure that had all this stuff going on but now we want to build another thing and it's a live collaboration. It's not that interesting. The point of our company was to do ML infrastructure. We're going to build our platform the that and use Convex because that's how we're going to store the metadata about this or build UI or something.

JASON LENGSTORF: You're seeing Convex as sort of the, almost like the kind of glue utility layer for the data� the data storage operations that just kind of inevitably crop up no matter what you're building?

TOM BALLINGER: I think so, right.

JASON LENGSTORF: Is there, like, a specific use case� or, I guess, is there a threshold that you need to clear, where it makes sense to reach for Convex? Or a point beyond which you wouldn't reach for Convex? Like, are there error bars for where you wouldn't�

TOM BALLINGER: Right now, it's closer to the latter. It is really quick to get started. This platform� you know, I can understand wanting to wait until there's a customer bigger� much bigger than you that can say, look, x is a $2 billion company and it's built on Convex. You wait for that before you build your own $2 billion company. You reach the limits of, okay, I can see where I would like these other features and now I need to build them myself. We think that it's a very� it's a modular thing, in some ways, where you need your own, you know, effective lambdas. We realize, we have to offer those on our platform because it really does� it is a bummer when you say, oh, I really like these features but once I have to step outside and it's no longer as useful. The goal would be, no, it really still is useful to have this data be reactive.
I'm doing the classic copout, yes, you should use our tool for everything. I'm excited for it. I'm building something from scratch or adding to an existing tool, just one page where I need some live collaboration or I just need this data to update live and I don't want to worry about it too much. So green field stuff. Oh, I need it somewhere�

JASON LENGSTORF: Optimizing for speed. If you are rapidlyprototyping, you're trying to validate an idea, this is the way you� the fastest way from idea to production, is what you're betting on right now?

TOM BALLINGER: Yeah. I think that's fair. The founders would say, no, we're the database people. The goal was to build a Firebase that would scale. I have to be careful. What I'm excited about is I like building small toys. They'll say, it's not for toys. The architecture of this is, it's both designed from the bottomup for web developers, but also, within the constraint of, oh, but it does need to scale. There are a lot of interesting things there. Hamstringing it there, in order to scale, we will need to do these things.

JASON LENGSTORF: This is actually� you're stepping into something that's probably more of a rabbit hole than we want to get into. It's the classic conundrum of developer startups because whenever you have a developer tooling startup, the people who are the most engaged with developer tooling are developers. And, their needs don't exactly align to the needs of the company's that will eventually become the paying customers' so you're trying to find the right way to both serve the needs of a developer who wants to play and experiment and move fast and learn things and also serve the needs of these companies that need to scale and be enterpriseready and be secure.
What a lot of companies, including� I make this mistake all the time, where you sort of conflate these things and you find yourself talking to a developer and you're talking about Iso21000. This is a fun� I don't know, there's maybe like a meta episode on this, on positioning for dev tooling startups.
I don't want to burn all our time on that because I want a lot of room for playing with this. Since Convex is Reactive data. Chat, if you behave, this is be an interactive episode. I want to make sure we've got enough time to do that. If you don't mind, I'm going to take us into Pair Programming mode. So, before we get into it, let's talk a little bit about the captioning. We've got Vanessa with us today, from White Coat Captioning. Thank you very much for being here. That's made possible through Netlify, Nx.
We're talking to Tom Ballinger. Make sure you go and give a follow on Twitter. I don't know why my browser just decided that I can only see mobile Twitter. Look at this, if I go here, I'm like, hey, I don't want to be on mobile Twitter and it's like, yeah, you do.
We're talking about today. If you're having fun and want to learn more, come out to Jamstack Conf. We're going to have some more details. I talked about this yakshaving post.
If you want to see the captions, they're on I might be able to get the closed caption button on Twitch to work. I'm digging into how to make it work. Hopefully soon, we will have fullblown, captions togglable on and off. Fingers crossed, I can figure that out.
So, y'all...I'm excited. Let's do this. I'm going to go over to the page. Tom, what should I do first?

I'd go to Quick Start. I would visit the docs and say, hmmm, I know what's going on. I don't need to read these docs or Tom's going to help me. Hop to Quick Start, unless we have some existing project we're converting, I'd start with these instructions.

JASON LENGSTORF: I'm going to pop this up and pull up my terminal. Let's do this. I'm going to, first, just copy/paste. So, let's do a Next app. And I'm going to call this� what should we call this? What are we building today?

TOM BALLINGER: Yeah, I mean, sort of chatty to make it interactive. It could be� I don't know. I don't know. That's the problem with names. You gotta pin yourself down.

JASON LENGSTORF: Let's do Reactive Toy.

TOM BALLINGER: Great. Great. That'll get me in trouble.

JASON LENGSTORF: On this show, we don't take anything seriously. We'd call the database a toy. [Laughter]. We're going to run through the Create Next App flow. This is an example?

TOM BALLINGER: That'll pull from the� I think from the repo, not even the last published version. I published an update the other day and it's just pulling that project from the Canary branch or the latest Next thing.

JASON LENGSTORF: Nice. Nice. Nice.

TOM BALLINGER: This could be a small enough project that it shouldn't be a cheat. We may� if the goal is to build a chat app, if I remember right, we might just have one after this. We want to understand it and then we can poke at it.

JASON LENGSTORF: So I'm in here. We've already installed everything. Yes, there's Node modules. We can NPM Run Dev.

TOM BALLINGER: Cool. We skipped a step here. This was a good test� on the start� oh, I see. I see. This is a bug. It is telling you, this is how you start. Back in the Quick Start, there's one more thing we need to run first.

JASON LENGSTORF: "Npxconvexinit."

TOM BALLINGER: And you create an account.

JASON LENGSTORF: Open in browser. Great. I'm opening in browser.

TOM BALLINGER: Looks the same to me. If you're good, you're actually read it.

JASON LENGSTORF: Totally read it.

TOM BALLINGER: Yeah, yeah. This is an account our� because you're not running the database, we're running it for you. We need to give you the tools for the database.


TOM BALLINGER: I can vouch for them. I thought they were pretty reasonable when I read them. [Laughter].

JASON LENGSTORF: I have four projects remaining. That's cool.

TOM BALLINGER: Free projects.

JASON LENGSTORF: Now if I go back, I have� okay, so we have to run a different command here.

TOM BALLINGER: We're actually going to need two terminals for now. I'm working on a thing right now to make it just one. We're going to need to run the NPM Run Dev and the NPX Convex Dev for the backend. Cool. That one's� people have seen that before.

JASON LENGSTORF: That's ready. Here is our�



TOM BALLINGER: This is good. This is not a chat app, it's a counter. We'll have to write code so that's good.

JASON LENGSTORF: Look at this. We have persistent state. So I'm adding, adding and adding. Doing whatever I just did there. And when I reload the page, we've got the Flash, that's a thing we can fix. It brings back that state so we have already persistent state, which is superexciting.

TOM BALLINGER: Yeah. Yeah. I want to emphasize [audio cutting out] two tabs so we can see it in both or open, you know, the dashboard on You've got the essence of it. Right. It must be coming from somewhere.

JASON LENGSTORF: If it's in two tabs�


JASON LENGSTORF: Ooohhhh! Look at that, y'all. That's cool. That's that reactivity right out of the gate so now what I'm curious about is how it actually works. I made a mistake here. Ctrl+z, "get init." I'm going to open it again so that our� that's better. Now I'm going to foreground this. I'm hitting Ctrl+z to suspend the process so I can do stuff and bring it back. That's not a thing that anyone needs to care about, but I like it.
Walk us through this. What has happened in this example?

TOM BALLINGER: I'm going to assume some knowledge of React and maybe a little bit of Next. The gist in Next, right, is there's a Pages directory and when you open the app, you're going to be sent to this next thing.

JASON LENGSTORF: I'm going to fire up a� a the thing? Do the thing, there we go. So now I have a liveshare setup.

TOM BALLINGER: Oh, sweet. Okay. Awesome.

JASON LENGSTORF: In the ping window, there's a chat so I'll just throw that in here. So now, you and I will both be in here and then if you want to give us a quick tour, I can follow you around.

TOM BALLINGER: Cool. Oops, I'm just going to move that to another computer here...can I send direct messages to myself? New message� I'm only logged into Twitter on this other device.

JASON LENGSTORF: Yes, you absolutely can directly DM yourself.

TOM BALLINGER: That's exactly what I'm doing this. DM'd. I'm back here. It's not showing up on my top list, I probably have to open it. Oh, no. Fail to send. I'm going to send it to my good friend. [Laughter]. Hey, please don't steal� [Laughter].

JASON LENGSTORF: So this one is a private window, too, and it's still� still synced. So, no stateful stuff happening here. This is cool. This is very cool. And so I'm going to poke around in a couple things here while I'm waiting for you to get joined up...


JASON LENGSTORF: And I think it should show me as soon as you arrive. There you are. So, yeah, let me just follow you and I'll fullscreen this and why don't you give us a tour?

TOM BALLINGER: Great. Yeah. So, I'll start with, you know, in a Next app, you've got this index� I have to ask about the flipped� showing the file tree on the right?

JASON LENGSTORF: Okay. I'm going to show you, are you ready? Collapsed, open, collapsed, open. And my code didn't move. [Laughter].

TOM BALLINGER: That's just correct, isn't it. Excellent point. That seems like the right way to do it. So, the gist is, I've got a Next page and unfortunately� we wanted to keep it close to the Next examples, there is CSS, we use it to style stuff. Whatever. [Laughter]. Down here, we've got a button and we've got a� where's our Use Query? Of course, the way that React code works is we put the declarative stuff at the bottom. This one says, hey, the String Get Counter, I want to call that with the argument clicks and that may return undefined because we're not serversiderendering this. If it is, use zero. Otherwise, get the value from that, stick it in this counter thing. Also create a function called Increment that I'm getting from this used mutation. I'm not calling this code yet, but this is a callback I could use to increment that thing. That actually takes arguments so on the next line, I'm creating a new callback. The first argument is "clicks." The second one is "one." We can pump it up. This is React noise. We don't really need to have that here. We could� we could pop that out if we're not worried about these rerenders.
And then, down in our code, we just have a button that calls that thing and then counter's just data and we can just render that. I keep scrolling down at the React stuff. We're saying counter is that live data. Use Query is a hook. Imagine, there's a Use Effect that causes this or a state that causes it to render that rerenders this component and that's telling you about React. The gist is, it's a hook and that means we can update this thing when we need to.

JASON LENGSTORF: And is this� so, I ran that NPX Convex Init. Is this all of the code? We're importing Used Query and Used Mutation from the generated?

TOM BALLINGER: The generated happened to be in the repo. The Next Dev would create it. There are two other files that are pretty important that we need to look at. In the UI, hopefully this is a reasonable interface in saying, yeah, give me the updated value of Counter.

JASON LENGSTORF: How did the� so, walk me through the arguments here. Get Counter? Do I have to call them a certain thing?

TOM BALLINGER: They corresponds to� if we have the TypeScript support set up, we'll get a nice completion. They are strings� I'm going to chance it. Let's just see. If I start� type a quote, here, and I type� oh, I'm not seeing it.

JASON LENGSTORF: I think sometimes the� yeah, here it is. It works for me.

TOM BALLINGER: Oh, interesting. Okay.

JASON LENGSTORF: If you let me take over for a second. I'll quote and it shows me Get Counter here. And then�

TOM BALLINGER: A second argument, which looks like it's supposed to be a counter name. We were modifying Clicks before and returning a number and if we want to not render undefined, we can do the question mark, question mark. Get Counter and Increment Count happens in the Convex folder and that's how we define our backend.

JASON LENGSTORF: I see a Convex folder here. I'm still following you, so if you hop in there, we'll go with you.

TOM BALLINGER: We've got a Get Counter here, some fancy syntax, not too bad. This is what� this is our transaction that runs on the database that says, great� might call it a query. This is a view. Hitting a "get endpoint," we'd get down to the SQL� we can walk through this. Increment Counter's the other one. This will run every time we send in that message. Increment Counter runs every time we send that function. Get Counter is pretty special in that it will run� you can imagine it runs maybe, like, 10 times a second and we're doing cool� cool, read, set, watching in the database to make this efficient. The idea is that this is running all the time. If it were to get a different result, it would run again.

JASON LENGSTORF: The query itself is coming in from the generated server. Okay, I get that. And, it gives us� we run the query and we have the pass a function into the query that does the� the query logic.

TOM BALLINGER: Exactly, yeah.

JASON LENGSTORF: So we get a database, all right. That makes sense. We get the counter name. This is arbitrary. We can name it whatever we want.

TOM BALLINGER: If we change that name right now, we'd see the autocompletion right now.

JASON LENGSTORF: Right. Right. Then, we have the table, which I'm assuming that we've defined somewhere else, that we'll look at shortly.

TOM BALLINGER: I'll say, roughly, yes. Yeah.

JASON LENGSTORF: We're filtering by the name, which is the counter name. So whatever we name it. We could have multiple counters on the same as long as we pass a different counter identifier. We're doing an append. It's not a single entry we're updating. We're saying, the clicks count is 5, the clicks count is 6 and we're describing the most recent one?

TOM BALLINGER: From this query, I could see that is what you would assume. Let's go ahead and look at the schema. If we go to that dashboard, in a browser�


TOM BALLINGER: Should be. If you want to open another terminal, you could type "convex� there's a command to get it. Good feedback. We should do that.

JASON LENGSTORF: And I've already authorized.

TOM BALLINGER: This is the counter table and we're looking at it and there's four columns. They've got underscores before them. The other two are� you know, on a JavaScript object  I'd call this a document store so I would describe these as they are two properties, which are counter and name. I'll describe it as a row of this table. There's only one row on this table and once we read the mutation, it is updating that one row. We got more rows if we made more counters.

JASON LENGSTORF: Coming back in here, this first is just to break it out of being an array.

TOM BALLINGER: Exactly. The other interesting piece, we're doing this querybuilder stuff. This query doesn't need to be efficient. Something we could do here� for starting out, that I would probably do instead, I would delete all this and say ".collect." And say, let's just get the whole thing and then take the first item. It's going to say, like, there might not be a first item. know, we could say something like "counters." I won't do it because we need that Next line where we're actually� you know, we need to update this data, too. The thing I'm excited about here is you can just use JavaScript to describe these queries. We're pushing it down the database for efficiency. Don't do a full scan, there might be a million rows in there, there are indexes, we can cleverly get the right rows.
That way, when all my logic in here is JavaScript, it's quicker to iterate. I get to say, oh, it's just JavaScript, I know how to say, is this an array of length zero? Great, do this other thing.

JASON LENGSTORF: Okay. So let's maybe pop into this increment counter and break down how that's working.

TOM BALLINGER: Yeah. This one's a little bit bigger, it looks like. Line 4, which is kind of fancy. But the gist is just, "increment Counter," use mutation. Counter name increment, the next arguments for that. This is a function running on the server any time we call our, you know� every time "increment" gets called back here.

JASON LENGSTORF: That actually answers a question here. Back in this index, we have what looks like builtin code. But this is actually user code. We define the signature for these� not for this one, but for this one here, "increment." We define what this is and so that, like, when we use the query, we're passing in the counter name. Right. So, we're calling this query and then that's the argument for it and then when we use a mutation, we're passing in the query name and then we have an arbitrary function, which is this one here. We're passing in the "increment" function. That makes sense and helps connect dots on how things are defined.
Let me go back to following you and let's walk through the logic. So we've got the Counter doc, same, general deal. Exact same as the query.

TOM BALLINGER: Looks like we don't have� sorry, distracted by another message. We're going to say, okay� this is an async thing here, find the row where the name is equal to the counter name that we passed in. This is a "where" clause, select the right row in a SQL statement. Was there anything there? If not� and this is the piece where when you were asking, this must be defined somewhere else? Right now, there's not a schema saying there is a thing called "counter table." These four lines of code ran, causing us to do the insert.

JASON LENGSTORF: This starts feeling sort of like Mongoy. Like a document store. I don't need to define a schema. I just need to say, put some stuff in here. So, this is one of those things I think causes very active debates, where you know, for speed and just being able to get up and go, it is really hard to beat this, where the code defines the schema.
For longterm maintainability, people would argue strict schema and migrations are going to help as you scale and grow. How did y'all land on this because I know one of the things you mentioned the founders are into is scale. When you're looking at document scores and schema defined in code, how do you think about that?

TOM BALLINGER: Totally. I don't want to argue against where I'm going. It's defined in code and defined based on whether this line of code is ever run. Based on the history of the stuff running, which does scare me a little bit but it also feels great. So what we do is if you pop back to the dashboard, we can see that down here, if you go to the bottomleft, there's a "generate schema" button. This is code you would dump in schema.ds. Your TypeScript deletion gets better. When you access this table, we can't say, I don't know, that has to do with the actual data. Basically, the overall approach was, add a schema later. Some people, the very first thing they do when they're designing the model, with do the schema. That's optional. Hey, you don't need to have a schema if you don't want. Likely, you know, in most of� for me, it's once I'm on a team with more than person or once it's over the course� it's all in my head for the one hour I'm coding and if I have to code on this tomorrow, I like a schema.

JASON LENGSTORF: What I like about this, this feels like it gives me a little bit of the "have your cake and eat it, too" model. Early, I don't like the restrictions of every time� crap, that's not going to work the way I thought, let me rewrite this code. If I have to back out schema stuff and burn down my table and start again, it gets so cumbersome to build so I love the idea of being able to be a complete chaos monkey the entire time and once you land on something that you're like, okay, I'm going to stick with this, then you generate the schema and that feels� I do like that� it feels good. Obviously, you have to have the discipline to actually do that and say, yes, I'm going to lock myself down. It feels better than just throwing yourself a complete, like, all right, I'm going� it's like the idea of, you know, the way when you build for yourself, you almost never follow the best practices of working on a team, I'm not going to do TDD when I'm prototyping, but you probably should but it's so, dang cumbersome to do it that you don't.
What I like about this it is kind of lets you be irresponsible at the beginning, without� without the tradeoff of, okay, now I need to go rewrite everything to be responsible.

TOM BALLINGER: This generated schema's kind of nice. It's based on what we see in the table. It may be you have mixed types. If we wanted a schema, we could copy and paste that in. Probably fine here. You do have to look at the data. It doesn't solve all the problems. You still may have mixed data in your database, you have to decide what to do. When you're prototyping, you hit the "clear table" button. When you have real data in there� right. Probably you want a schema before you get to that point. The other thing that's unlocked by schemas is data. I'm going to be doing this kind of query, with index builder. Great, please maintain this information so that these queries can be efficient. That's all the stuff we would like you to do when you need it or when you're ready for structure.

JASON LENGSTORF: We've got 40 minutes left. Is there anything else we need to review?

TOM BALLINGER: I can handwave and say, this is either� create� this is either create a document or if it's already there, modify there and [audio cutting out] not too fancy going on here.

JASON LENGSTORF: Okay. Here's what I think we should do. Here's my pitch: We've got a lot of people who, I'm sure, would love to make a mess of anything that we build, if we can publish it and get it to them live. So, we've got this basic setup here. Could, don't make me regret this. Could we make an actual chat room that people could just� how do you handle auth? Can we do a login real quick?

A Google Author something, Auth0 lets you use Google or a variety of things.

JASON LENGSTORF: Do we have time for that? That might be a stretch?

TOM BALLINGER: I think it's fun to start without auth. It's very reasonable� when there's messages in there we don't like, we can go to the dashboard and change a boolean and change it. We can absolutely do auth. It's going to be your skills, clicking through dialogues, which I'm sure you're excellent at.

JASON LENGSTORF: Have a lot of practice. I want to, very quickly, mockup a chat form. So, we want a basic form. That form is going to include a label, HTML4 name. And that one, we'll probably want autofilled. It's also going to be "name" and type "text." And then we'll not another one of these. A text area. Label, HTML4 that message or we'll just say "message." And that one is going to be like this. We'll do a text area with an ID of "message" and a name of "message." We don't need anything else on that, do we? That gives us the name and our message.
And for the barest of styles, let's give this form a class name of "chat" in here and is going to be actually no style. So, we'll go for label and put "text area." Text area. And we'll do a width, 100. And a display block. And we need to go and apply that to our form so it actually needs to be "styles."

TOM BALLINGER: Okay, cool. Yeah, yeah. Great.

JASON LENGSTORF: Not beautiful. Not the worst. It'll function. So, what I want to do, then, is I want to take this and hook it up to Convex. Oh, and then I guess we'll need another piece, which will be� let's go with a div, class name, styles, chat log.

TOM BALLINGER: Let's show the chat. Great.

JASON LENGSTORF: Each one of these, I think will need to be a� how to we want to do this? We'll probably make them paragraphs. Paragraphed with a�


JASON LENGSTORF: And for now, we can "test user." And then we'll do another one for message...say, like, "something awesome."


JASON LENGSTORF: And we probably want these paragraphs. We'll leave the paragraph fine.

TOM BALLINGER: User styles going to help us out. I really appreciate� you're going a lot faster with this than I would.

JASON LENGSTORF: Go with a chat log. We'll give it a height of, like, 300. Let's give it a border of solid black and we'll do an overflow y of "scroll." And an author� no, I want to do chat log key is going to have a display "flex." We'll do a "justify content." That's default. We'll do a gap of, like, one REM.

Is it justify content?

JASON LENGSTORF: Author will be a width of 100 pixels and an overflow x of hidden� whatever, we'll just do hidden. Now, we've got overflow. And let's also make it "text align right." So we can make this shorter. Let's make it 60.


JASON LENGSTORF: 80. There we go. So, this is our� this is going to be our general set up here. We've got a chat log. Let's also make this...that way, we've got a little differentiation of what's going on here. Ultrahigh design. I want to hook this up, I want to get this running. This will be our template for a chat message and this is the piece we need to hook up to the database, which means I need to create a query and a mutation, right?

TOM BALLINGER: Exactly. Yeah.


TOM BALLINGER: We could modify the existing ones or be good and create new ones.

JASON LENGSTORF: Let's create new ones, .TS. And in this one� what are you doing? Get away from me. Why don't you like this?

TOM BALLINGER: I'm confused that I can't even see it. What happened here? Is it out one level? No, it seems fine.

JASON LENGSTORF: I'm going to look in this one. I'm going to copy/paste to get us started and collapse this. We'll say...

TOM BALLINGER: What's going on is when we� we can do this. You might have to� there we go. Great. Perfect.

JASON LENGSTORF: I just needed to save this because it was missing this piece?

TOM BALLINGER: That's a bug. When you create a file, it should be empty. That's helpful. Why can't I see this? "Get chat." "Get chat." It might be out of sync. I've got Convex there. We don't need me. You're set. Okay. [Laughter].

JASON LENGSTORF: Okay. So, I'm going to shrink this up a little bit more so we have a little bit more real estate over here. Now I have, let's see, my chat name. And then this is going to be "chat doc." And I want the chat, right, not counter?

TOM BALLINGER: Yeah, sounds great. Previously, it was set up a singleton junk, only a single row. It's a simpler one now. It is "awaitdb.querytable.collect."

JASON LENGSTORF: Do I not want a chat log? You know what, you're right. Let's not worry about it.

TOM BALLINGER: This will still be a log. Imagining if every chat message is another row in this table, then we want to get all the rows. Just come right out.

JASON LENGSTORF: So this is going to be actually "objects," right?

TOM BALLINGER: And we're going to be lying about it. Object array sounds great.

JASON LENGSTORF: So then I don't need a chat name. Query got simpler. Null. Chat doc will never be null.

TOM BALLINGER: It might be an empty array, but those render great. Might call it "chat messages," or "chats" or "messages" instead of "chat doc," because now we've got docs.

JASON LENGSTORF: You're right. In here, I should be able to swap out "use query," "use mutation." And this one is going to be "get chat." It doesn't have a second argument and it doesn't return null.

TOM BALLINGER: We can do proper loading states. Or, yeah, it works great.

JASON LENGSTORF: Then we just "" And then we'll get a message and we'll do one of these and we'll bring this thing in there.


JASON LENGSTORF: And then I can do a author�

TOM BALLINGER: Making a schema now. Perfect. Author sounds great.

JASON LENGSTORF: Message.message.

TOM BALLINGER: Great. And now we get into, you know, it may be time to create a schema or we type it as "any" and move on for the day.

JASON LENGSTORF: For now, just to get us through�

TOM BALLINGER: It's a tool and we can bend it to our will.

JASON LENGSTORF: Yes. We will fix this later. For now, we're just making stuff up. Right. We need to set a min width on our chat so we'll do a width, if I can type, 100%. Now we have our� we have our "get chat" so then the piece that I need to do next is in here, I'm going to copy/paste this one and call it "add chat message."


JASON LENGSTORF: And we will...think about this a little bit. So, I don't need to do this part anymore.

TOM BALLINGER: You need very few of these. We need the insert still.

JASON LENGSTORF: We don't need a counter doc. I don't need a "replace." And I don't need a name.

TOM BALLINGER: I guess these can be "author" and "body" or something? Yeah, that works great.

JASON LENGSTORF: Author's going to be a string� or a message. String. And that means I need this one to be "message." Okay. So then we get our author. What did I call this one? "Get chat." We called it "chat table." So we're going to "chat table." Honestly, just�

TOM BALLINGER: Just stick it in. Great. Perfect. [Laughter]. Yeah, yeah, that's great.

JASON LENGSTORF: I'm typing it here.

TOM BALLINGER: Sorry, I read it as destructuring. Just typing.

JASON LENGSTORF: Easier than I thought, but it doesn't like something. Failed to compile.

TOM BALLINGER: If we look at the terminal where it's running the� what are we whining about? [Mumbling].

JASON LENGSTORF: Oh, it did a thing. Hold on.

TOM BALLINGER: It looks like there must be a file called "increment counter copy."

JASON LENGSTORF: When I duplicated this�

TOM BALLINGER: Got it, got it. This is great. We have� we just wrote this live devs thing and we've got folks that worked on the sync engines at Dropbox so we know we can watch files. This is great.

JASON LENGSTORF: We want to add a chat message and this gives us "add message." I guess we can even just call it "add chat message." And I don't need that piece because what I can do down here is...on "submit," we're going to..."event." And then we'll do one of these, prevent default. And then we can get...I guess we can�

TOM BALLINGER: You do it by ID, is that right?

JASON LENGSTORF: "Consts data equals"� I think we can do the new message is "author." Will be "data.getname" and message will be "data.getmessage." Form data is freakin' magical and we send in our new message.

TOM BALLINGER: We wrote too many lines of code to actually work.

JASON LENGSTORF: HTML form element. I screwed this up. Event...crap, what's the...does anybody remember what the form element is on a submit? is "new form data" and then it's "event..."

TOM BALLINGER: Form data� new form data on the form element� that looks like what you wrote.

JASON LENGSTORF: Should have been "target"? Current target. Let's see what happens. So then I get my new message, which it says "could be null." So let's do this...

TOM BALLINGER: Write some defaults or� yeah.

JASON LENGSTORF: Let's do this, if not new message...not author...or� obviously we can't�

TOM BALLINGER: Clientside validation. This helps users.

JASON LENGSTORF: We don't want any mess so we'll do nothing if you don't have the information in. And you, how dare you! Does not� so, you're just going to�

TOM BALLINGER: But the "not" should get the author?

JASON LENGSTORF: It's going to be strings.

TOM BALLINGER: Perfect. [Laughter].

JASON LENGSTORF: When in doubt, hit it with a hammer.

TOM BALLINGER: Yes, you want to do this on the client side, you ought to also do it on the server side. Probably not going to want to do it right now. That's a whole world of stuff. Maybe this works. It might even go all the way through...but if it doesn't, we can look at the dashboard�

JASON LENGSTORF: Oh, I don't have a submit button. It works if I hit "enter" on that so let's add a "submit" button. So we'll do a "button type submit."

TOM BALLINGER: Pretty good on first try.

JASON LENGSTORF: Now, the real moment of truth here�

TOM BALLINGER: You already had your moment of truth. It already worked.

JASON LENGSTORF: It did already work, but did it� it did, indeed, persist. Check it out, y'all. If I try to send it empty or try to send it incomplete, it doesn't work...


JASON LENGSTORF: It� make sure my submit button actually does work. It does, indeed, work. So, y'all, we just built a dang chat. So, let's deploy this.


JASON LENGSTORF: So I'm going to "get add" everything. Let's see, I probably shouldn't include the generated?

TOM BALLINGER: No, I would. You can set it up to do it in your CI environment to regenerate it.

JASON LENGSTORF: We've got all these pieces so I'm going to "get commit." Chat is working. Let's create a new repo. Let's create this one.

TOM BALLINGER: This is advanced magic. Wow. Don't have to click buttons.

JASON LENGSTORF: So, I can just hit that. We're going to push an existing local repo, that's this one. Going to give it this name...and it's public. We want to add a remote. It's going to be called "origin."

TOM BALLINGER: This is so good. This is so good.

JASON LENGSTORF: Because I'm using the Starship terminal button, here's or repo. I'm going to head over to Netlify�

TOM BALLINGER: Now, let's do it.

JASON LENGSTORF: I'm going to add a new sit, import an existing project and then I'm going to choose my Learn With Jason and let's search for Convex. So, you know, there it is. All right. And, we've got a Main. It sees it's a Next site so it's going to automatically do what it needs to do so I'm just going to deploy this thing.

TOM BALLINGER: Cool. We have instructions for how to deploy with Netlify but I think we can pull through without them.

JASON LENGSTORF: Do I need to set any variables?

TOM BALLINGER: An environment variable and a custom command.

JASON LENGSTORF: Let me head over to my variables. I need to add an environment variable and that environment variable I need is...

TOM BALLINGER: "Convex Deploy Key" and you can get it on your dashboard if you have a safe way.

JASON LENGSTORF: Let me pull off. Oh, okay. Let me show everybody what this looks like. I clicked on "settings," there's a button to deploy key. I have it. Now I'm going to go back to Data and pull this up here and then here, I have� it's called "Convex Key."

TOM BALLINGER: Under "deploy," under "key."

JASON LENGSTORF: Make sure this is hidden. I've created my deploy key and what was the custom command?

TOM BALLINGER: On the docs, if you look at hosting Netlify, we've got�

JASON LENGSTORF: Hosting Netlify.

TOM BALLINGER: It's run NPX Convex Deploy and then do the build. This is when you push, we're going to deploy whatever functions you pushed so that your frontend and your backend are in sync.

JASON LENGSTORF: Uhhuh. Here's my build command. I'm going to overwrite that. Save it. We head back over to Deploys. This one will work but it'll be missing things so I'm going to deploy again...and, we're� we're off and running.

TOM BALLINGER: Yeah. It was helpful to hear, both a few things we ran into, but� like, other things I would add to this Next is, all right, we're doing the bareminimum, which is whatever is submitted by a client, we're stuffing in a database and in general, it'll work. There are some limits to what types you can stick in a database. I'm starting to get uncomfortable not having a schema so I might add a schema.
Those queries, we're doing a useful, but leastinteresting query, which is give me every row of this table. The fun part of Convex is we can now make it, let's only show the last five messages or when you send a message, disallow it if that same user has sent a message in the last 10 seconds, which isn't going to work great for us right now. You can take that application code that makes your chat app special or makes it not a chat app and stick it in those functions. It's a good iteration process.

JASON LENGSTORF: Actually, let's rename this site. I'm going to rename this to Convex Reactive Toy and I'm going to save. And now,'s the thing, though. Keep it clean because if anybody does anything bad, I'm shutting this whole thing down and you're going to ruin it for everybody. Get in there. Add some chats. Like, let's play. Right. Like, this is� this now works, right. Like, we've got� we've got some really cool stuff. I'm already seeing a bug, we need to clear the form. Otherwise, like, this is� this is really fascinating how quickly we were able to put together a real, live chat and now we get to see the moment of truth. Here's where every chat app breaks down is when someone posts the Next message...

TOM BALLINGER: You gotta scroll down, yeah, yeah.

JASON LENGSTORF: Although it does look like now that I'm scrolled to the bottom, it's going to stick? No, still gotta go down. This is the stuff that I think is really fascinating is, how quickly we're able to go and put these together and how spread out everybody is. Istanbul, Philadelphia, romaine. How much work it actually is to implement something like this in another stack. That's the piece that really gets me excited when we start talking about these sorts of apps and projects and tools, is that, you know, like, I didn't have to go and think about what Reactivity was going to look like and how was I going to write the piece that was going to listen for the database update and then do the requery and do this whole thing. This is the kind of stuff that just feels� when I was telling you earlier that this sort of felt like magic, this is the magic. We spent less than 30 minutes� we did, like, 25 minutes of coding and in that 25 minutes of coding, we built a full chat app. It looks like it was designed by my dad, who's an old sea guy and it's definitely got a lot of edge cases and bugs. We need user account verification and we need� you know, we need a lot to still go in here. But how frickin' quickly we were able to do that.

TOM BALLINGER: When I see a chat app, I think maybe that's a transient data. You could have a web socket server that doesn't have persistent state or it's moderatelypersistent. If you could open the dashboard in a tab next to this one, you'll see both we have� we've got all that information, but also we have� this data can be modified. Okay. So, great. We've got this one open.

JASON LENGSTORF: Right. Here's my two apps, sidebyside, one of them's a private browser window.

TOM BALLINGER: Could you open the Convex dashboard? Click on...that's funny� refresh this. We're so excited about reactivity, you may have to refresh this here. Oh, you know what, it's because we're doing this� go over to Prod. Here are our messages. We can modify these. Presumably, you're going to build your own admin interface. If you can find one of these we can find on both screens and doubleclick on it, you can change what it says there. For me, that emphasizes that this is mutable data, presumably� normally you would write mutations, maybe you'd write admin mutations for moderating. You can doubleclick on one of those messages and see that update live in the app is pretty fun.
Right. Not a great idea to be� we can change, it's working or it's not working� right. Yeah.
[Laughter]. Perfect.

JASON LENGSTORF: How the tables have turned, chat! Haha ha!
[Laughter]. Oh, I've been waiting to do that for a long time. [Laughter]. So, this is� I mean, this is� like, and I love that I didn't have to refresh anything and I when I go back over to our app's the published one, it's all updated. This, I think, is incredible because it just shows, like� so, again, moderation. Right. If this was live chat, we had a bigger audience, the chat was full of terrible people, which it's clearly not, then we� we would be able to, very quickly, you know, hit the Moderate button and it would remove the message and I don't have to go and build that. You know, there's so many cool things that we can do. Right.
I find that to be just absolutely fascinating. Like, I love the� the amount of boilerplate and toil that just got lifted off my plate, trying to build a Reactive app. That gets me really excited about Convex and sort of what door this opens.

TOM BALLINGER: Uhhuh. It's a different� we use this� have a Reactive style at Dropbox and the desktop client, at Observable for live text editing and at Redux, we had this. It a different. When everything is automatically reactive, you write simpler code because you don't have all these change detectors, all this integration with all this polling. But it is� you know, the idea that anything on the page could change at any time, you have to [Indiscernible] even closer to the rules of React. Thinking about these transitions, another message might appear here without having done anything. The UI could change because this user's permission could change. There's of this space that's been explored. Gosh, if anything could happen at any time, does that make the developer responsible for the state change that could happen? Instead of the user sitting at an invalid page, they're at the new page.

JASON LENGSTORF: I think that's� yeah. That is a really fun, um� it just� it gets me thinking about cool things that we could build, that I would have previously� like� descoped because I would have said, I don't have the time to actually maintain that infrastructure. To build a fun, interactive piece of an app. If it is fun, I want it. If it accidentally turns into this huge maintenance nightmare [audio cutting out] I'm going to crash on my willingness to continue building it. Because it just becomes too much. You find yourself in these scenarios, I built a demo, it's fun, people enjoy it, but it breaks every six days. GitHub issues and stuff like that and you kind of give up because it's too complicated to run the thing. Creative and joyful ways of using the web get stifled by the cost and overhead of the boilerplate, the foundational technology so what I really like is this is going to allow me to unlock absolute chaos for a lot of things I've been thinking about for a long time because there were too many steps, too many links in the chain.
So, already got some schemes, got some ideas, things we can do.
What about you� we got about five minutes left. So, chat, if you've got questions, throw them in now.
Y'all should go click that evil laugh. That would be a good alert, if somebody wants to grab that.
Okay. So, while we're waiting for the chat to come up with any questions they may have, where should someone go if they want more information? They saw what we did today, they want to get deeper?

TOM BALLINGER: I'd say, join the Discord. Lots of communities are good at this. We're still early enough that we really are excited to hear from people about, hey, I'm using this, this thing broke, or I wish I had this feature. Please join this. That link, that's great.

JASON LENGSTORF: Let me copy the real� the named link here...

TOM BALLINGER: Yeah. Yeah. Perfect. We'd love to hear about what you're doing with this. A lot of information is in the docs and we did the speed run where we jumped straight to the Quick Start and that is a fun thing to do if you like poking around and trying things but you can also go through the tutorial. I'm working on a tutorial right now, hoping to publish it tomorrow. Something like this, we start with this� it's more of a chat app. Here's a quick code tour, here's this line, here's what they're doing. The docs are� check out the docs, but you don't need to have read every doc on here before you ask questions because that's not a reasonable way to learn technology. As you saw, the steps are not too bad to get something running and then you can start iterating from there.

JASON LENGSTORF: Yeah. Yeah. Superfun. And so since you have the� the tweet� I guess, the tutorial coming out. I'm going to say, everyone go give Tom a follow on Twitter, to make sure you are aware of that when it goes live.
Getting some love from  hey, How are you doing? Does this work with Vue?

TOM BALLINGER: Sure. Basically, no. Yes. I mean, yes. [Laughter]. I have done a lot of, look, this works with Svelte. The core experience is just, hey, do you have a React app? Previously, it had been, do you have a React App with TypeScript? That's how many of us have professionally created sites now. We are now spreading out a bit. Totally, no, it doesn't need to be TypeScript. Let's highlight that experience. Similarly, our CEO made an unofficial solid client. It ends up being about 50 lines of code to write one of these clients. I would love for you to go to the Discord and say, hey, I'd love this to work with Vue. Here's the React client while we're still changing so much, that's the part we want to hear from people about. There's lots of discussions. The stuff you were talking about before, where, how wide do you go? Do you build something developers want or start on your checklist of the enterprise features? We have a lot of discussions, is it time to branch out? Informally, we'll say, oh, cool, this weekend, I made a Rust client. I had a stream with Lee the other day� Lee that works here� we made a Python client. It's very doable, but you're going to be doing more work.

JASON LENGSTORF: So, the code generation, that's what gives us Use Query and Use Mutation so the piece you have to build in the port is the implementation of Use Query and Use Mutation; correct?

TOM BALLINGER: We have a client that lets you say, I want to subscribe to this thing, every time it updates, tell me about it. Every time there's a series of updates, please batch them and apply them consistently. You have to say, these three queries updated, how do I view in the store that they've updated and how do I do it in a batched way. One of these is not valid without the other one. You can do simple stuff where you run code when it updates.

JASON LENGSTORF: I just realized, are we wrapping� we're wrapping in a provider. So that's the piece that kind of pulls all this together. So what we're doing is, to use Convex, you're providing a global store and the hooks, here, Use Query Use Mutation, they're tying into the store. As I am writing a plugin for Solid, Vue, Svelte, I need all the hooks from Convex into this global data store and whatever the equivalent of a Use Query or a Use Mutation works.
And the thing that's exciting, if you look here, y'all, this is early days and the thing that's really exciting about early days is if you're looking to get involved in a community, the very best time is right now. The thing works, but it's missing a ton of features because the community's not there yet. If you look at a way a lot of people have established their open source careers, it's through being part of these initial explosions of, okay, who's going to build the Convex Svelte client, the Vue, and that is a way to establish some expertise. It's a way to give back to the community. It's a way to really dive deep and learn how this maintenance works and it's before the point where all the lowhanging fruit is solved and becoming a maintainer becomes ultradeep. You can't become a React contributor today. There's so much context and nuance and mess in the history of what React, the library has been. You have to really, really want it to be there.
But to write 50 lines of code, that's a cool opportunity and I hope that's something some of you chase down if you've been looking for open source.
Tom, this has been great. Anything else you want to share with folks before we wrapup?

TOM BALLINGER: Second all the stuff you said. This is early enough, you can build the x client and I'd love to work with you if you do. If you're in this Discord, let me know. I have some bandwidth to do pair programming. Because it's early days, I get to do fun things, I'm going to work with the user, building an app today and then it might be a feature request or find a bug.

JASON LENGSTORF: Excellent. All right, y'all. I think we're going to call this one a success. This episode, like every episode, has been live captioned. We've had Vanessa with us today, from White Coat Captioning, thank you very much, Vanessa.
So, make sure, while you're checking out things on the Learn With Jason site, that you go and take a look at the schedule. I'm actually going to do a bit of a dealers choice on Thursday. So, I don't have a guest. And I don't know what I should do so I'm going to put up a poll on Twitter for what I should do on Twitter� or, on Thursday. Make sure you head over there. I think I'm going to put in options like "play Fortnite." Add this to� subscribe on YouTube, like on Twitch. They feel silly to say out loud, but they make a big difference if people get shown this content. Like, share, subscribe, because we've got amazing list of guests coming up. Got a bunch of people who I have not put up on the site yet.
Please do let me know if there's anybody you want to see on the show or somebody you want to bring back that you love learning from. Hit me up [audio cutting out]. With that, Tom, I think we've got a great episode in the can here so let's go find somebody to raid. Ben's going live. Let's go see semantics dev. Okay. Ben, thank you very much. Chat, as always, it's been a pleasure, let's go find somebody to raid. We'll see you all next time.

Closed captioning and more are made possible by our sponsors: