Build a Full-Stack Serverless App With Next.js & Fauna
with Shadid Haque
Web devs have more power than ever to build ambitious full-stack apps. In this episode, Shadid Haque will teach us how to build a TikTok clone with Netlify Functions, Fauna, and Next.js!
Topics
Resources & Links
Transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show, we have Shadid Haque. Shadid, thank you so much for hanging out with us today. How you doing?
SHADID: Pretty good, pretty good. Thanks for having me.
JASON: I'm thrilled to have you on the show. For folks who aren't familiar with your work, do you want to give us a little background on yourself?
SHADID: Yeah, absolutely. So I'm Shadid. I work for Fauna. So Fauna is a serverless database with native GraphQL API. We are a globally distributed database, and Fauna works really great with Jamstack type of applications. So yeah, I work for -- I'm a developer advocate. Yeah, I've been coding for some time. Before Fauna, I used to work for IBM. Really, really glad to be here today.
JASON: Oh, you were at IBM too?
SHADID: Yeah.
JASON: Nice, nice. What team were you on?
SHADID: I was actually on a front-end team. So the carbon framework.
JASON: Wait, you were on carbon?
SHADID: Yeah, I was.
JASON: That's amazing. So, were you there when Robin was the manager?
SHADID: Yeah, I think so.
JASON: Nice. That's super cool. So Robin used to be my manager as well before I moved over to carbon. This is so interesting for everybody who's watching. I'm sorry. (Laughter) Anyway, so let's talk a little bit about what we're going to do today now that neither of us is at IBM anymore. So one of the things that I find really fascinating about front-end development in general is that we saw a push a while back, and we saw this in bootcamp curriculums, and the way people talk about things. You see this pressure to become a full-stack dev. What that meant in 2010 is like you had to be able to get into virtual machines and configure docker and do stand up a database on AWS or Google Cloud or whatever and figure out how do you do scaling, lock down the security, all of these really complicated things. Then on top of that, you also had to be able to built a front end and the middleware in between that would connect those things. And you had to be able to do the styling and the front end to make this experience look and feel nice to use. As we've moved into today in the '20s, it feels like that gap has significantly closed. It's not really feasible to become an expert in literally everything, right. You can't be seven devs in a trench coat. Like, we've got to specialize. So I think that what we're seeing is, you know, we've seen front end, specifically this idea of front-end developers who know enough serverless to be dangerous, are capable of building what would be considered full-stack apps but without having to get into container management and database scaling and figuring out how to do, like, massive security and horizontal scaling and database sharding and all of these things that get really, really complicated. So this is a fascinating time to be a front-end developer because you almost de facto become a full-stack developer. And that's what we're talking about today, right. Building a full-stack app. But why don't you talk about what the actual plan is here so that everybody who's maybe intimidated by the idea of full stack can get a sense of what do we actually build in a full-stack app.
SHADID: Yeah, absolutely. So basically, a full-stack app is technically when you are able to store data in your data layer, when you're able to retrieve that data, and you're able to do all sorts of things. For example, if you have an application like Facebook or Reddit, all of these social media type of applications where you can pretty much do prod operations, where data is stored somewhere in the server. Then the user can log back in, and you can retrieve that data and share things with the users in between or retrieve your data, things like that. So pretty much --
JASON: And CRUD is an acronym meaning create, read, update, delete. When you talk about apps, those are the four buckets of operations that you're going to do against any database.
SHADID: Yeah, yeah. And previously, like, if you know something about Ruby on Rails or PHP from back in the days, you have to have this entire stack running pretty much to do all these back-end operations. You have to have a dedicated database that is connected to your PHP application, which is purely a back-end application. You sort of have to create a rest end point or you can do all sorts of stuff, like MDC was a pattern back in the day. Things like that. You had to do all sorts of -- like, you had to write a ton of code to save your data, save user sessions, authenticate your users, manage user state, cookies. There's tons of stuff that went into that. However, now with the rise of Jamstack and all these wonderful services that are available, Netlify, Fauna -- we're going to talk about Fauna as a data layer -- Netlify functions, Next.js, all this stuff, you can be purely a front-end developer, and you should still be able to pretty much create all the functionality of a back-end stack relatively quick and easily. So that's huge. Think of creating an entire startup in a week. You can roll out a product very fast, very quickly. That's kind of what we're trying to do here today. We're going to create like a really simple sample application to demonstrate that, to demonstrate the power of Jamstack, Next.js, Netlify Functions, Fauna as a database.
JASON: Nice. Yeah, I think -- and that's really interesting. We're looking at, what, we've got 82 minutes. What you just described is ambitious as hell. We're talking about setting up a database. We're talking about setting up a new app. We're talking about building a front end, building middleware, building a back end, all in the span of, by the time I stop talking about it, will be about 75 minutes. So this is not a short order for what we can build, but with this new kind of model of we can combine existing tools that take the heavy lifting of the boilerplate part, the base-level foundation that is the same for 95% of apps. We don't have to do that anymore. We just get to work on what our app needs, what our app does that's different, right. So we're only building features. We're only building value. We're not doing boilerplate or foundation because that's handled by tools that take care of that for us. So because we have so much to cover, I'm actually going to move us right along into programming so we can get as far as we can here. So let me do a quick shout out before we get started to our captioning. We've got Rachel from White Coat Captioning here today doing those captions for us. Thank you so much, Rachel, for being here. And that is made possible through the support of our sponsors, Netlify, Fauna, and Auth0, all of whom are kicking in to make this show more accessible to more people, which means a lot to me. We're talking to Shadid today, and I'm in the wrong window. So let's pull this up. You can get over there and give that a quick follow. Let me just shut that down. Yeah, get out of here. There we go. Get over there, give it a follow. And we're going to be talking about -- so, Fauna is going to be our database. We're going to use Netlify Functions for our serverless layer. So let me drop that in here. And then we're going to use Next.js to build the app itself. And that's as much as I know. What else should I -- what should I do next?
SHADID: Yeah, so we are also going to try to hook up Algolia. Although, it's optional. We might not have the time to do it. We might just touch a little bit. So, Algolia is another great service for full-text service. It also jives really well with Netlify Function, Fauna, and Next.js. For storage, I think we're going to use Cloudinary, which is just storing blobs on the internet. It's a storage service. So, yeah.
JASON: Blobs on the internet is actually the title of my memoir.
SHADID: That's pretty cool.
JASON: (Laughter) It's not. Okay. So what is my first step here? Where should I start?
SHADID: Yeah, let's start with the diagram, just to show people.
JASON: That's right. Diagram here. Okay. So this is a quick and dirty architectural diagram. I recognize logos, but why don't you walk us through what's happening here.
SHADID: Yeah, so our main front-end application is written in Next.js here. So what we're doing, we're communicating Next.js through our application. Like, our application layer is going to be solely Next.js. So that's where our front-end code lives. Now, when you put functions in your API directory, all those functions actually become Netlify functions. So they automatically become your serverless functions. So we're going to take a look at that. So that's where Netlify comes in, Netlify functions. Okay. We're going to deploy this whole stack to Netlify as well. You're going to see how easy it is to deploy to Netlify. And we're going to use GraphQL to easily communicate to Fauna. So Fauna is going to be our data layer. Fauna is a database, but you can use it to do a lot of your back-end stuff. And we're going to see how easy it is to actually use Fauna to act as a back-end service. And on top of that, we can have Algolia to do some, you know, full-text searching, all sort of searching that you see in Amazon or TikTok. You can do a ton with Algolia. We won't probably have time to touch too much on that. And yeah, Cloudinary, we're just storing our videos, blobs, in Cloudinary.
JASON: Excellent. Okay. So let me get that out of the way. If I wanted to get started here, I'm expecting I need to create some code. Are we using a folder, or do we want to use a starter?
SHADID: Yeah, let's start with a starter kit. So I have created a starter kit.
JASON: Okay. So I'm going to grab this starter kit, and I'm going to GitHub repo clone. Then I'm going to --
SHADID: Jason, do you want to do this, or do you want me to code along?
JASON: I mean, we can do it a couple ways. I can open this up in a VS Code live share session, or I'm always happy to be hands on keyboard. That's easy for me. Why can't I -- oh, yeah. This is what I want. So I want to GitHub repo fork. We're going to go into -- was it TikTok? Yep. Then GitHub repo fork and we'll put it in Learn With Jason. Oh, wait. Hold on. Fine, fine. I'm going to do it the other way. I'm going to fork it from here because I don't remember how to do it. So, let me fork this. I want to fork it to the Learn With Jason repo. Don't need any of that. Okay. And so then we're going to say the get remote -- or no, shoot. Get remote rename? Origin upstream. Does that work? See how much I remember. A-ha, I did it! Learn With Jason and TikTok starter.git.
SHADID: All right. Awesome.
JASON: All right. Here we go. So I have that set up, and I can open up the code here to look at it.
SHADID: If you want to do the live share, I can jump -- hop into it as well.
JASON: Yeah, let's do it. I'll get set up here. Continue. Okay. Okay. I've copied that. So I'm going to drop it in your Twitter DMs.
SHADID: Awesome.
JASON: So there's a link for you to join. And then I'm going to go back to your profile and come back here. All right. So this should allow you to join up.
SHADID: And it's opening. Just give it one second. Okay. Open Visual Studio Code.
JASON: I think this should show once you join here. Okay. So we're just waiting for the live share servers to connect all the pipes. Or I guess the tubes, as we all know the internet is just a series of connected tubes.
SHADID: Oh, no. It says version mismatched.
JASON: Crap, okay. I thought I was in the latest, but maybe I'm not. Restart to update. Let me try again. Here it comes. Here's the service. Let me get that live share going again. Okay. We're just going to throw that in your DMs one more time. And there it goes. All right. This time it's going to work. First try. Chat, how many of y'all have used -- don't. Not now. I don't know what it's doing, and I'm unhappy about this. Okay. We might have to -- you might have to just tell me what to code if this doesn't work here in a second.
SHADID: Okay. I think I'm in. Yeah, I'm in your folder. All right.
JASON: Let's see. I don't see -- oh, here you are. Okay. Yeah, there you are. All right. So where should I go? Like, where are we starting?
SHADID: Okay. So first of all, let's do npm run dev, just to see what we got with the starter.
JASON: Okay. Oh, wait. I need to npm install, don't I.
SHADID: Yeah.
JASON: All right. Let's run the npm install. This will take a second. Here we go. Let me run npm dev again. What don't you like? Not supported.
SHADID: Are you --
JASON: What -- hold on. For whatever reason, my terminal opened in Node 12. Let me just change that. Start server. We're running at 3,000. Let's get this going here. So here's 3,000. I'm going back out. And show time. Hey, we have got ourselves a starter app. We can see Next.js, Fauna, and GraphQL. We have an upload and log-in button. We have this logo. All right. Yeah, this is cool. So do you want to walk us through where we're starting, and we can jump in? It's already unhappy. I'm assuming we're going to fix that. All right.
SHADID: Yeah, all right. So let's dive into the pages directory. In the pages directory, you're going to go to the index.js file. This is where our -- this is our entry point of the app. This is our homepage. This is where we're going to have all the TikTok feed show up. So this is where we're going to put them. If you take a look at the --
JASON: How about this. I'm following you now. So I should just jump to wherever you go. Yeah, here we go.
SHADID: So here we're in the app.js file. You're going to notice a couple things here. First of all, we have our components wrapped into this layout component. In Next.js, a layout component gives you global styling pretty much.
JASON: And that is this header and these log-in buttons and all that stuff. That's all coming out of layout.
SHADID: Yep. So let's dive into the layout. Again, this component has the log-in, log-out button. The nav bar and all that stuff. So this layout is under the components folder. Here we also have a log-in and sign-up components. So when you like on that log-in button in the nav bar, it shows up. And again, this is just HTML at this point. None of these things do anything.
JASON: Right.
SHADID: All right. So if you're familiar with React, you should be able to follow along no problem. Like, this is just some simple forms with some form handling and handle changes and all that. It wraps all the components. All right. Now, the next thing is the Apollo provider. Again, we're using Apollo provider to kind of call GraphQL.
JASON: Right.
SHADID: So we're going to call Fauna or GraphQL. Again, we're going to use Fauna database as our back end. So let's take a look. The writer component comes from Apollo client library. Again, we installed this package from Apollo. This takes in a client object, which is also provided for you in the Apollo client here. So this is all provided for you in the starter code. This is all some basic GraphQL setup, checking for headers and stuff like that, like what GraphQL end points we're going to hit, things like that. When we cache and stuff like that. So these are all boilerplates. You don't really need to go deep into it. It's all provided for you. All right.
JASON: Okay. And so what is ultimately happening here, if I'm reading this correctly, is with the Apollo client down here at the bottom on lines 22 to 25, we have the cache, which is the way that Apollo says if you make a query, Apollo will hold on to that so you don't have to make the same query multiple times. It just makes things a little more efficient. That's why you would use a client versus just making a fetch request. Then the link is so that we can pass along whatever we're passing in as well as authorization headers. So it looks like we are using cookies to get the Fauna session, and then we're getting a token out of that session here. Good. Then we just pass that along. So if I'm understanding correctly, what that means is whenever we send a request to Apollo client, that request will include this authorization header.
SHADID: Yes, exactly.
JASON: Great.
SHADID: Yep, and on line 12, if you take a look, we have the Fauna secret token. This is a token we're going to generate in Fauna and plus it into our environment variable.
JASON: Should I go and set up -- should I deploy this to Netlify?
SHADID: Yes, let's deploy this to Netlify just to get started.
JASON: So let's get it deployed. What I'm going to do is stop the server here. Then I'm going to check -- we haven't made any changes yet, so let me git push upstream origin main and make sure we've got all the code up there. I think we do. We do. Then what I'm going to do next is I'm going to run Netlify init, and that will start setting up our Netlify site. I'm going to create and configure a new one. We'll put it on my team, and we'll say TikTok clone. Oh, somebody already did that one. So let's go TikTok LWJ. Yeah, that's fine. Our build command, it auto detected npm run build. The director deploy is .next. I think that will get changed by the next function. What is that? That is a lie. Let's try that one more time. Huh, what are you doing? Well, that's annoying. Let's go set it up manually, I guess. So I just ran Netlify open to get this in here. You can see it set up the site, but I need to hook it up to a git repo. So I'm going to go to build and deploy, and we want to link to git. So I'm going to choose GitHub. That'll get us authorized. Then in the Learn With Jason org, I can get TikTok here. We're going to deploy the main branch. Yep, all that is good. Let's deploy the site. And what we should see in here is it should pick up like the next -- should pick up it's a Next site and get us a running site. So let's let that run.
SHADID: Yeah, just to give you guys some context, there is a Netlify.toml file in here. So that's also -- it's actually using the Next.js plug-in.
JASON: Good, good. What's up, Alex? Welcome, everybody. And did you -- let me look at this Netlify.toml. So for future projects, you actually don't even need to do this. We'll automatically install it for you. Netlify knows it's a Next project. We'll just automatically set the right things up so that Next works.
SHADID: Awesome.
JASON: So here we go. It's all doing the stuff. And in fact, let me look just to make sure because we want to make sure we're using the -- okay. You didn't install a version of the plug-in. Good. That's what we want. That way it'll use the latest version, which is what we're after. And we're function bundling. Almost. There we go. Deploying. And this'll be done in just a second here.
SHADID: All right. Yeah, so while this is happening, we can go to Fauna. Fauna.com.
JASON: So this -- whoa. That deployed but pieces got missed. Wonder what changed here. We can debug that. In the meantime, let's go to Fauna. I'm going to log in because I've already gotten the count. I'll log in with my Netlify account because I'm already signed into that for sure. Here we go. And all right. So I have a bunch of databases already. I'm going to create a new one, and we're going to call this TikTok clone. Do I want --
SHADID: We can use classic because it's the simplest.
JASON: Okay. And so I've got that. Then we've got all of this kind of database collections, indexes. So GraphQL is the thing I gravitated toward because I saw that in the diagram. What's my next step here?
SHADID: So next step. We're going to show you how easy it is to get your back end up and running. So all you have to do, we're going to go back to the code. So let's create a schema.graphql file.
JASON: Am I not following you anymore? I'll keep following you. All right.
SHADID: So let's create a type. Let's create a type called user. Our users will have names, obviously. Users will also have emails, which is going to be a string. Actually, let me just copy/paste a couple things. So users will also have a password, right. So this is our user model. For every user, we are going to have posts so users can post. So let's also copy/paste the post model. And again, Fauna is a NoSQL database, but it can be a structured as a SQL database as well. So here we have a relationship between user and posts. And just to do it in GraphQL. User post is already a post type. The post has ID been -- well, I don't think I need ID here. The post has a title, tags, and it belongs to a user.
JASON: Got it.
SHADID: This is our basic GraphQL schema. Now all we have to do is we're just going to upload this in Fauna.
JASON: Okay. So just upload this straight up. I'm going to import schema. We'll go to GitHub, Learn With Jason. Got a schema.graphql. It goes. And if I look at the docs, I can see that I have -- here's my CRUD. I can create, I can read, I can update, and I can delete. It looks like I can do that for both users and posts.
SHADID: Yep, and that's it. We pretty much have a back end right now. So we have a database. We pretty much have a pseudo back end. Again, just to clarify, Fauna at its core is a database. So we're not like Hasura. They're more like application servers. That being said, you can run app sync Hasura and still use Fauna as your database and combine them.
JASON: Got it, okay.
SHADID: So Fauna, at its core, is a very flexible database that just gives you GraphQL native APIs. So at its core, it's a database, but you can use it as a back end as well. I kind of wanted to emphasize that. Cool. So now what we can do is we can generate a security key. So right now we can't really -- yeah, we can't really connect to the database. So we can create a security key. So let's make it a server. You can name it.
JASON: Do I need to keep this off screen here?
SHADID: Yeah, you can just hit save.
JASON: Okay. So this is a secret. Now, I don't want that secret to be visible to anybody, right?
SHADID: You don't. However, after this stream, we can delete that.
JASON: Okay. So let's do that. We'll just roll the key over before we take this thing live. So we've got a secret key here. Then I need to get that into our environment. So we called that -- I can just do a Netlify env set. Then it was in Apollo client we had next public Fauna secret. So let me create that. Next public Fauna secret. So when I set this, that will send it up to Netlify. It says it now set that environment variable. If I go and look in here, we can also see it. Like, I'll go to my settings and build and deploy. I look at my environment. Now it exists. So that's a really, really handy way. The Netlify CLI has gotten such a big power-up in the last year. So it's really, really nice to use now. And that's one of the things I love about it. So now I've got this secret set, which means that I should, theoretically, be able to log in. Except, we don't have a user yet. So do I need to create a user first?
SHADID: Yeah. So let's go ahead and create a .env.local as well and have that secret here so we can debug it locally.
JASON: So it'll also pick up if I run Netlify dev now. It'll pull in that Fauna secret. So it'll run for us, which is really nice. So now we have a local version of it running with the environment variable from Netlify so we don't have to manage multiple environment variable files. Makes my life a little easier.
SHADID: That's really neat. I did not know that trick. That's really neat.
JASON: And chat, I see y'all trying to use the commands today. I have not had a chance to fix the overlay yet. Something is wrong, and I haven't had a chance to look at what it is yet. I will debug my overlays, but they're not working today. So sorry about that. Okay. So we have the environment variables setup, which means we can actually send authorization here.
SHADID: Awesome. So one cool thing about Fauna is it gives you a GraphQL.
SHADID: Sorry, I think I just cut off there for a bit. Sorry about that.
JASON: Yeah, so Fauna gives us GraphQL. And we can see that.
SHADID: Sorry, Jason. Just go ahead.
JASON: Can you hear me?
SHADID: Yeah, yeah, I can hear you.
JASON: Okay. So Fauna gives us GraphQL, and I can see that here, right. What should I do next?
SHADID: Let's go to the Fauna dashboard. Here what we can do -- oh, not here. The Fauna dashboard. So yeah, here. What we can do, we can create a new user. So let's just create a new mutation.
JASON: Oh, mutation. That's right.
SHADID: Yeah.
JASON: Okay. So I'm just using control-space autocompletes for me. So it created my user. I'm going to come back out and look at my collections. We get a user. So one thing that we would want to do in a production database is make sure that we're not sharing the -- like, this shouldn't be stored in plain text. We want to encrypt it. We don't have time to do that today. But make sure that you are actually using encryption for passwords or you're going to have a bad time. (Laughter)
SHADID: We're going to take a look at it in a bit, though. Fauna does have a built-in support for that.
JASON: Oh, nice.
SHADID: Yeah, we'll take a look at it in a bit. I just wanted to show you.
JASON: Yeah, get up and running fast.
SHADID: Yeah. Okay. So let's create a couple posts. So let's create --
JASON: Do I need my user ID or anything?
SHADID: Yeah, so we do need that user we just created. You can click on the tab, and it will open another tab on the top.
JASON: I think I jumped the gun on you. So we have a title. Stop it. Stop helping. Then we can do -- let's get the tags. We'll just say, like, test. Then for the author, is that just the ID?
SHADID: Yeah, that's just the ID.
JASON: And what we're going to get back is the post. Then I want to pull the author. This should give me the name. Oh, I screwed something up.
SHADID: For author, I think you have to do squiggly bracket.
JASON: Like an ID?
SHADID: Yeah. So squiggly bracket. So you get rid of that.
JASON: Get rid of this?
SHADID: Yeah, and then ID. Let me take a look real quick.
JASON: Oh, connect to a document of type user. My autocomplete is working now. Well, it was. What just happened? Now let's try. Ta-da. So if I go in here and go query and I want to say find host -- oh, wait. I guess I can find the user by ID. Then I can do name and posts. In posts, I want the title. Oh, crap. That didn't work. When I go here, my user is 8643. So what have I done wrong here?
SHADID: Okay. So let's take a look.
JASON: Did I screw this connect up here?
SHADID: It's showing your name. I'm not sure. Maybe we just have to refresh.
JASON: Oh, like refresh GraphQL entirely. Let me do that. Let's go in here. No, it doesn't like that. Let's see if we can get find post by ID. So I've got -- I'll get a post. I'll get copy ref. Great. Let me go back to GraphQL. Find post. Then down here, we should be able to get the title. Then the author, and the author should have a name. So it works this way, but it's not working this way. Interesting. What did we do wrong? Do we need to, like, create a two-way binding here or anything?
SHADID: No, no. This should just work. I think it's okay. We can move along. I think it's just something with the caching or something.
JASON: Okay.
SHADID: We can just ignore it for now and move forward. So now what we can do is we definitely -- so if you look at the GraphQL stuff, we definitely need a way to list all the posts, right. So in order to do that, we can create a custom mutation. Sorry, a custom query, actually. So right now there's no way to get all the posts. You can only get posts by ID, right. So let's create a new query. That will give us a query that will give us all the posts.
JASON: Okay. Are you typing? Okay, got you.
SHADID: Yeah, so again, I'm just going to quickly tell you, like, what's going on here. We created a new type query, and we created a list post query. It returns an array of posts. Here we said resolver. Now, in any GraphQL type of situation, like any GraphQL server, a resolver returns to some sort of function. In Fauna, it resolves to Fauna query language. So that's like the underlying database language. So yeah, we're going to take a look at it in a bit. So let's go ahead and save this. Let's go back to our Fauna dashboard in GraphQL and replace the old schema. We don't have to merge. We can just replace it. And it will not delete any of your data. So it's completely safe. Now we just upload it. And now if you open up the doc again, you'll see that a list post is available. All right.
JASON: So we'll get the title, the author, and I want the name of the author. Let's see. Was not implemented yet. So I need to implement a function, it looks like.
SHADID: Yes. And this is where -- yeah. This is where the FQL comes in. So let's go to the function. There's the function, all right. So you'll see that function is not implemented yet. Again, I do have a prewritten function for this.
JASON: You want to just drop it at the bottom here, and I can copy/paste it out?
SHADID: Yeah, absolutely. So list post.fql. Just going to drop it here. You can paste it, and I'll just go about expanding what it means.
JASON: So let me paste this in. We start up here. We can get the size, after and before. Then we have a let. It says match post by TS for timestamp?
SHADID: Yes. And again, this is all in our documentation site. I kind of -- I found the most complicated example, just to touch base on everything. But yeah, you can look into the documentation to see kind of what it means.
JASON: Okay. All right. So now what I run this, it says I need an index of posts by timestamp. I'm assuming I need to create an index in indexes.
SHADID: Yes. So let's go ahead and create an index.
JASON: Posts. And we wanted posts by TS. Okay. What do I do here? Like, what's next?
SHADID: Okay. So actually, let's not do the post by timestamp. We're going to get back to this, okay. So let's go back to our GraphQL schema.
JASON: The schema?
SHADID: Yeah. All right. So just because we have a paginated true, we actually have to create a timestamp manually. We might not have time to do that today, but we'll get back to this one later, if that's cool. Let's go ahead and -- sorry. I'm trying to get my things. Let's create a log-in function first. So let me retrack a little bit. I kind of wanted to show the list post through resolvers. But I think I jumped the gun a little too quickly. Let's create a user register function first. Then we can get to this one in a bit. So let's create these two mutations. You don't want to create your -- save your password like that, exposed in the thing. So what we're going to do is create two new mutations. One registers a user. The other one logs in a user. Okay. So let's create the token type here. That's going to be the embedded token. All right. So what we're going to do is create like a really short token, short lived. Whenever a user logs in, you get back a token. Using that token, you can query different resources in Fauna. So that's why we can't really get a list post right now. I just realized you can't really do that because we don't really have token right now.
JASON: Gotcha.
SHADID: So yeah, let's do the registered user and log-in function first.
JASON: Okay. So I need to upload this schema so that it'll kind of register those functions for me, is that right?
SHADID: Yeah, but before we do that, however, we should also create a new index that will find user by email. For now, let's just get rid of that.
JASON: Okay.
SHADID: So we created a new index where users -- like, we can find users by email. Then we created a mutation to register user and allow function. All these functions will get created, and we're going to see these functions in a bit in Fauna.
JASON: And this find user by email, the reason this is going to work is because the email matches a field name here, and Fauna is smart enough to know if I'm looking for a user by email and the field filter is email, if it can find a matching field, it just uses that.
SHADID: Yes.
JASON: Okay. Okay, great. Let's get this thing uploaded. So this one is gone. Let's get a find user by email. I'm going to put in my email. And we'll just get a name. So that's great. But we don't have our functions yet so we can see these are both not implemented yet. So I would assume we probably want to start with registered user first.
SHADID: Yep, absolutely. So let's go ahead and do the registered user. And again, I should have probably done this one first just to -- you know, that function was kind of scary, the first one. So my bad. So a registered user FQL is going to look something like this. It's a really simple function. Now I can kind of explain what this FQL does. So basically query and lambda are two key words that define a function in Fauna. Email, password, and name, in this order, we just pass in those two variables pretty much. So we're telling Fauna, whenever you create this user, register this user, make sure to hide the password variable. And that's what it does.
JASON: Got it. So when I go out here then, let's create a user. So I'm going to go mutation. I want to register a user. And it wants my name. So I'm going to put in Jason2. Email. And we'll go with one of those. Then password, I'm going to put in as super secret. Then what I get back is let's look at name, email, password. Theoretically speaking, this password is not going to be what I put in, right? It's going to be a hash or something.
SHADID: Theoretically speaking, you're probably not going to get a password back.
JASON: Cannot return null for non-nullable type password. Okay. Let's get rid of password. What did I do? Set my name as super secret.
SHADID: Oh, I think I know what happened there. So if you go back to the function, in our schema, if you --
JASON: Oh, we did it backwards.
SHADID: Yeah, we have to follow the order. So we have to do name, email, password. No, no, no. I think if you go to the function --
JASON: Email, password, name. Okay. So email, password, name.
SHADID: So we have to do name, email, password. In the function, actually. If you go up to the function.
JASON: Oh, you want to change it around in the function itself?
SHADID: Yeah, yeah.
JASON: Got it.
SHADID: Because it's not -- so let's just switch the order to name, email, and password.
JASON: Whoops. Okay. So we'll save, go back here. Undo what I did. So we'll go name, and then delete these. All right. So name, email, password. Run it again. That looks more correct. I'm going to go into collections here and take a look at my users. And I have a Jason3. Good. And we don't have a password listed anymore.
SHADID: Yep.
JASON: All right. So it's just gone altogether. But it's not actually gone. It's just hidden. It's not available in the database. It's still present in terms of, like, we can have somebody log in with that password.
SHADID: Yep, yep. And that's that.
JASON: All right.
SHADID: Let's do the log-in. Let's go ahead and do the log-in as well. All right.
JASON: So that's going to be a function. Log-in. All right.
SHADID: You'll see that the log-in is unimplemented. And I'm just going to create that function for you here. And query just defines this is a lambda function. We pass in the email and the password. What we get back is a secret. And yeah, a couple information. Again, this, we could have just not had that in there. It would still work. But yeah, I just put it in there. So yeah, basically we're calling the log-in function, which is built in and baked into Fauna. We're giving it time to live, like the token is going to be valid for 1800 seconds. Password is from there.
JASON: Got it. So when I go out here and go to my user and I say I want to log in, I'm going to send an email. That was the email that worked, I think. Then we're going to send in a password. I'm going to give it our super secret password. Then what I should get back is a TTL, a secret, email, and a user ID. Anything else I missed here? Nope, that looks right. Okay. Value not found at path zero.
SHADID: Okay, that's weird.
JASON: Let me just double check I got my credentials right here. It was Jason+3. Yep, I did that right. In our function, we had it set up as email, password. So in our function, log-in, email, password.
SHADID: Okay. What we can do is get rid of that user ID field. So get rid of the line 15 to 18.
JASON: Okay.
SHADID: Probably don't need that.
JASON: Save that. Get rid of the user ID. That worked. Good, good. Cool.
SHADID: All right. Awesome. So we pretty much have a log-in function right now. We're pretty much able to create and register a new user, log in the new user. We're able to create posts and connect to the user, all in the back end and through GraphQL. Now what we can do is start calling those mutations from our front end.
JASON: All right. Let me close this one. Let me close this one. Then we get back to the schema, which we don't need anymore. So let's open up -- where should I go next?
SHADID: So let's go over to -- so let's go over to our components. Let's try to do the log-in function first.
JASON: Okay.
SHADID: So here, if you go to the log-in component, you'll see that on click. This is the opening signup. So on click, we're just going to ramp up -- we're going to call the log-in user function. So basically, what we can here, we can -- let's implement this. So we're going to call the log-in function, right. So let me go ahead and just copy/paste the entire thing over here. This is the same user log-in mutation you're sort of using in that GraphQL playground. So I'm just going to copy/paste that over here.
JASON: Drop that user ID because it's not going to work, on 13.
SHADID: Oh, yeah. Right. And here I'm going to bring in the use mutation from GraphQL.
JASON: Okay.
SHADID: And I'm also going to import the cookie because that secret we want to save as secret. So next what we do, we create a function. So let's go ahead and do that. I'm just going to copy/paste it from the finished code. I think we're in a time crunch here.
JASON: Yeah, we're a little short on time, for sure.
SHADID: All right. Okay.
JASON: So what this is doing, use mutation from Apollo does the setup so we get the loading state, data loading error. Then it gives us the log-in func so we can execute the mutation. It doesn't call it right away because that would be chaos, and we wouldn't be able to call it more than once. So this is giving us the ability to call that log-in function as many times as we need, which is different than use query, if you've used Apollo's use query.
SHADID: Exactly. And here we are handling the change. So set state, this is just a basic React. A change handler. So we're just updating the state based on whatever the user inputs.
JASON: Wait, where did that state function come from? Did we need to declare that?
SHADID: Oh, yes. So we need to have a user state. So let me just do that right here. Then I can bring in the user state right there. Awesome. All right. So now once we hit log-in, we are going to call that log-in function. So if we go over here, we have default behavior. We're just going to pass in the state. Once it's logged in, we're just -- if it goes into error, we just console log the error. Once it's logged in, we want to show some sort of alert or something. So I'm just going to create use effect. So here we're listening on the data. So when the data changes, I'm just going to console log that. We're going to use that cookie to set a new cookie in our browser and it will expire sometime, whenever the TTL comes back. And we're just going to say, okay, user is logged in successfully. All right. And yeah, so let's go ahead and do that.
JASON: Yeah, let's give it a whirl. Yep, it's still running. So I'm going to just head back out to our dashboard here. Let's go to local host. I need to go to 8888. Cannot find client in the context or passed in as an option. Let's see what went wrong here. Did we get a different error? Let's try that again. Oh, that was the right one. Anything happen over here? Nope, sorry, here.
SHADID: Interesting.
JASON: Could not find client. So then looking here, we've got our auth link. We've got the client. We export default client. So when we get to our app, we import client at the top level, which should exist. So if we log our client, let's see what happens. One of one unhandled errors. But here's the Apollo client. It still says use Apollo client. What's going on here?
SHADID: Maybe we have to restart the server.
JASON: Yeah, we can give it a shot. Let's see what's going on here. Could not find client in the context or passed in as an option. Yeah, let's restart it. It still doesn't like it. So what don't you like? Got Apollo provider. Then we get into this index, upload API components. We have this one shared here. Use mutation. Okay. Why wouldn't you work?
SHADID: Okay, that's a tough one.
JASON: Chat, you seeing the problem? Are we looking right at it? So, data comes in, right. Then we have our log-in func. Do we get -- like, do we get this? No. So it fails before that. If I go up here, do we get this far? This is what I would consider to be high-level debugging. Let's try dropping this out. And drop this out. Let's see if that brings us back. So it doesn't like uncaught at login on 32. What are you trying to do? Whenever the data changes, it's supposed to console log the data, which it's not doing. Set state, login func. So we need this data. Okay. But we're never getting to this point in the app. So -- let's see. Do you want to try just copy/pasting your working log-in component from the final? Let's see if we -- maybe we just typoed something.
SHADID: Sure. Let me do that.
JASON: So the user ID is not going to work. Outside of the user ID, everything else looks the same. Here's our user login. Email, password. Good. That all is coming in. Yeah, let's give it a shot and see what happens. No, it doesn't like -- does not like that client. That client is just missing. Um, let's try -- let's see. Client from Apollo client. Then when we look in here, export default client. There's nothing in here that says this shouldn't work. Hmmm, okay. I'm stumped.
SHADID: Yeah, why don't we take a look at the final code for this and run through it quickly. Because I'm not sure why.
JASON: Do you have this deployed somewhere so we can peek at it? Or do you want me to --
SHADID: Yeah, so I do have it deployed somewhere. So just give me one second here. I'm just going to share the final code. You can just clone the repository.
JASON: Okay. Let me go get this. Then I'm going to move out to here. We will make directory called -- okay. Then we're going to GitHub repo clone. Let's move into it. And I'm going to npm install. Okay. Then I can open this one up. And I think I might need to give you -- get you a new live share here. Let's go to here. And I'll let you set the .env on this. Because I assume you've got one running.
SHADID: So that's the final code, right?
JASON: Mm-hmm.
SHADID: All right. So let me just get my .env. All right.
JASON: And I won't follow you while you set those so we don't give away the keys here.
SHADID: Yeah. So can I just drop it to your --
JASON: Well, I just sent you a live share so you can jump into the deployed code here.
SHADID: Oh, okay. All right.
JASON: But while you're doing that, I'll set up a .env.local. And I'm going to put some stuff into it. Okay. So those are set. I have a masking plug-in here so people can't see the values. And that should be enough for this to run. So let me stop over here. And we'll come over here and instead run npm run dev. Okay. So that is running. Now when I go over to local host 3000, we've got code running here. Okay. So if I search for jellyfish, it searched, but it looks like it didn't update.
SHADID: Okay. So the update is not implemented yet.
JASON: Oh, I got you. Okay.
SHADID: But if you open up the console, we'll see that. Yeah, yeah. Yeah, it's an unimplemented feature.
JASON: Okay, cool. So we've got like a setup here where we're showing -- you know, we've got the search. The search goes through. We get results back. So here are our hits from Algolia. Good. So let's maybe do a code walk through in the last ten minutes to get a sense of what changed here. So looking at the pages here, I'm going to go to the index. That brings up a feed component. So I'm going to go into this feed component. Let's talk about what's happening here. Are you in the live share, by the way? No. Okay. So we set up a get post query. This is that list post. I'm assuming you implemented the timestamp that we didn't have time to implement. Then in here, we've got Apollo use query that uses get post, which is this one here. That brings us back our loading state, data state, error state. Here's our search, which we'll look at in a second. And this is the column. So we've got a menu. We've got the search. Okay. So I'm going to collapse that. Now we've got the videos themselves. So the videos, each one runs to a video card. The vid just comes right out of our schema. So if I look at this schema, let's see what changed. Not much, right. Title, tag, author. Where did the videos -- did the videos get attached to the ID? How were you uploading those?
SHADID: Yeah, so every time you upload the video to Cloudinary, it gives you an ID. That ID becomes the post ID. This is why our list post kind of didn't work. Because we didn't have the ID or the timestamp.
JASON: Got it, okay.
SHADID: Yeah. So, yeah. Basically, that ID from Cloudinary becomes your ID in Fauna. We just upload that. So basically all the -- yeah.
JASON: Okay.
SHADID: You can also take a look at the pages/api.
JASON: So we've got an index video. Can you say more about what's happening in here?
SHADID: Yeah, so every time you upload a video, you call this function, and all the video metadata also goes to Algolia search. So that way you're saving all the data in Fauna, saving the blob in Cloudinary, but the stuff that you want to search by goes to Algolia search. So basically, you index those data and save that object to Algolia for searching.
JASON: Gotcha. Cool. Yeah, so we've got the videos being indexed. Then this is -- now that they are indexed, we can use Algolia to search for them, which this is what we saw in the console, where we do -- we get the index, the video index. Then we search within that index for the search term. Then it responds with that data.
SHADID: Yeah, exactly. And once we deploy this to Netlify, these become our back-end sort of functions, serverless edge functions.
JASON: Nice. Okay. So then -- these will get deployed as Netlify functions under the hood. As far as your development experience goes, you just build them like you always would. Netlify does that stuff under the hood. We've got an index page, right. Then we've got an upload page. That upload page has an upload component. It looks like we've got a file uploader that will post the form data to Cloudinary. Then it creates a post. Oh, okay. All right. So I understand what's happening. We go to Cloudinary and upload the video. Once we've uploaded the video, we get the public ID from the uploaded asset from Cloudinary. Then we reset the file in local state, get our user ID out of the Fauna session. Then if we don't have a public ID, we just bail. Otherwise, we create a new post with an ID being the Cloudinary public ID. We set a title to whatever the title is set to. We set the user ID to the current user's ID. Then we add tags, if there are tags. Otherwise, we upload an empty array. When we get data uploaded, we post an alert to show that. And then we get -- we call this function to index the new video. Okay. And that's what's happening here. We let you know that it's uploaded, and then we add it to the Algolia index. Then down here, this is a form that gives us the -- it's a file input. That file input takes a video name. It takes the tags. Then when you click this button t calls that upload file function we were just looking at. Okay. So that all makes sense. Then this video card is what actually displays it. It gets the video item that comes out of Fauna. And what I'm curious about is how you did this part, which is -- aha. So you go to Cloudinary. Then you just grab it by its public ID and append a .mp4, which means Cloudinary will serve it as an mp4. Then you're off to the races. Like, we can see here that it's working. Here's my 3000. Yeah, we've got videos running from Cloudinary so that we can see them right there in the browser. So you know, there's a lot of moving parts here, and it looks like something -- we got something out of kilter. Probably just missed a piece of foundational stuff, and that's why we're seeing those errors. But this is -- I mean, this is powerful. And again, this isn't a ton of code. So y'all can see this. The source code is up here at Shadid12/ticktok-clone. You can kind of a dig into how -- like, here are all the functions, the FQL functions you'll need. You have the schema you'll need. All the rest of it just works. You'd have to go to Algolia, get your index keys, go to Fauna, set up an account, get a secret key. Otherwise, you're off to -- you're ready to party.
SHADID: Yep, yeah.
JASON: Cool. Well, Shadid, this is some really cool stuff. Where would you recommend people go if they want to learn more about this? Like, if they want to go deeper?
SHADID: Yeah, absolutely. So first of all, go look at the Next.js documentation. Go look at Fauna documentation. And you can always follow me. I'm actually doing a tutorial series where we're just going to build a notion from scratch in Jamstack. So Netlify functions, Next.js, Fauna, and Algolia.
JASON: Where should somebody go for that?
SHADID: So I have a sub-stack, and I also have my Twitter. So please follow me on Twitter. It's most likely going to be on my Twitter and my dev too. You can also follow me on dev. It's Shadid12. So please follow me, and I'll let you know whenever this tutorial series drops. So it's going to be like a whole workshop type of thing where we just completely build notion from scratch using Jamstack and Fauna.
JASON: Nice. Cool. Well, with that being said, let's make sure you go follow Shadid on Twitter. And, you know, get on dev. Get into those Fauna docs, those Next docs. Get out there and deploy something, especially if you want to learn now. We've got something called Dusty Domains going on at Netlify, which is a fundraiser. Take any unused domain you have or -- I won't tell anybody if you don't, but you can ship anything. You don't need a custom domain. Ship it to Netlify and submit it here, and we'll donate $500 for every site you submit. There's no limit to how many sites you can submit. We have a bunch of great partners chipping in, doing matches. We're up to $100,000 total donated to charity. We're sending these to great charities. Go check all these out. Maybe consider donating yourself as well. But this is a really, really easy way to learn something new, to get something donated to charity, and to have a little bit of fun with folks. So go give it a shot, build something, ship it. Let me know you shipped it. I'd love to see it. With that being said, let's do another shout out to our captioning. We've had Rachel from White Coat Captioning here all day. Thank you so much, Rachel. That's made possible through the support of our sponsors, Netlify, Fauna, and Auth0. And while you're checking out things on the site, make sure you look at the schedule. We have a lot of really good stuff coming up. This, I believe, is our last stream of the year. We're going to take the holiday break, and we will be back on January 4th. Joel Hooks is going to be here again. We're going to talk about marketing automation. If you've ever been curious about how all that stuff works, you want to show people different pathways through a product, like during their lifecycle, Joel is doing cool stuff on that. I would highly encourage you to check that out. We're going to be learning TRPC, which I'm really excited about. I don't know anything about it. So I'm really excited for Alex to show up here and demystify it. We're going to get into Supabase and n8n. So many good things. Check out that schedule. You can hit this Google Calendar button or follow on Twitter to get more of our good stuff here. As always, you can find all the episodes on YouTube or under this episodes tab. With that, we're out of time. Shadid, any parting words for everybody before we call this one done?
SHADID: Yeah, thanks for sticking around, and thanks for having me, Jason. Really enjoyed it.
JASON: All right, y'all. This has been a lot of fun. Thank you so much, Shadid. We're going to find somebody to raid, so stay tuned. We will see you all next time.