Store User-Generated Content in Sanity!
with Espen Hovlandsdal
Can a CMS handle all the requirements of a full-blown app? Espen Hovlandsdal teaches us how to use Sanity and Netlify Functions to handle user input in Jamstack apps!
Resources & Links
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 LENGSTORF: Hello, everyone. And, welcome to another episode of Learn With Jason. Today, on the show, we have Espen. Thank you so much for coming back.
ESPEN HOVLANDSDAL: I've been great. How are you?
JASON LENGSTORF: I'm feeling -- feeling very fortunate to have some nice weather in Portland. I'm feeling, you know, cautiously optimistic that we are -- we're seeing some positive, lasting impact in the world right now. It's good. It's good. How about you? How are you holding up out there?
ESPEN HOVLANDSDAL: Yeah. Pretty good. Sun's shining in San Francisco and been social distancing for awhile.
JASON LENGSTORF: I get that, for sure. Well, yeah. So, you've been on the show before. We've talked about Sanity. But for those who are not familiar with your work and also with Sanity, do you want to do -- kind of a high-level overview?
ESPEN HOVLANDSDAL: Sure. Yeah. I'm Espen and I've been working at Sanity since the start of our little company, that's been growing rapidly. We're sort of a content platform, which is a fancy way of saying that we have a database in the cloud where you can store all your content and then we have a really kickass headless CMS to manage all your data. Yeah, just getting a lot of things for free in that sort of platform, all things from assets to APIs for fetching that data and manipulating that data with fine patches. History APIs are introspecting what's going on.
JASON LENGSTORF: Yeah. Very cool. Yeah. What I like about Sanity is I've always been a fan -- sorry, I got to turn the brightness down on my monitors. They're too bright.
What I like about Sanity is that Sanity is fast to get started with and it's completely customizable. You just kind of go in and define a schema for your site and that's really nice because one of the things that always bugged me about WordPress is it felt like I was going in and forcing a blog post to be something else and so with Sanity, there's a little bit more setup to it. It's not, like, magically done -- unless you use a starter. But what you're doing is you're saying, "I have this type of content and I want it to do exactly this." And you're building that. You're not saying, well, I want to treat my blog post with extra fields, like a custom-type content. That's a really nice experience.
But something that we talked about -- that I hadn't really considered, but we're going to do today -- Sanity is also good for storing user input. You can use Sanity as a database, which, whoa! I had never really considered that.
ESPEN HOVLANDSDAL: The database is sort of a schemaless data base. You can put a JSON document in there. You're not limited to what you define in your schemas. You can start -- import data directly to the database without defining any schemas up front. Because it's arbitrary schemaless, you can start. It is use for user-generated content. We have been focusing more on the structure content aspect, which is why we're focusing on the CMS right now.
You can put pretty much anything in there. You can use it for user-generated content. Obviously why -- just watching out for not allowing anyone to submit absolutely anything. You want Spam prevention in front of there.
JASON LENGSTORF: Absolutely. So, we're going to switch over and do that for just a minute. Thank you very much for the bits, I appreciate it. It's great to see everybody in the chat. What is up? How are you all doing? Don't forget that we have live captioning for the show. White Coat Captioning is on the call. You'll be able to watch the show with live captions underneath. Thank you so much for that. That is made possible by the sponsors of the show, Netlify, Fauna, Auth0 and Sanity. Thank you very, very much for that.
With that, you want to write some code?
ESPEN HOVLANDSDAL: Yeah. Let's do it.
JASON LENGSTORF: All right. Let's do it. So, let's go over to this view. And -- so, this is Espen's Twitter. You should go and follow. Oh, so we would need to add some code. This is the Sanity website. If you're unfamiliar with Sanity, go check that out. There's some cool stuff on here. But, for a quick overview, we're going to dive into it right now. I have a terminal. I've got my browser open. I'm ready to start. Tell me what would be our first step, here, if we're going to start -- empty folder. We want to get a site up and running.
ESPEN HOVLANDSDAL: The first part would be installing the Sanity interface.
JASON LENGSTORF: I'm doing a global install of the Sanity CLI. I've got version 220.127.116.11, which I host is the recent one.
ESPEN HOVLANDSDAL: That's the recent one, yep.
JASON LENGSTORF: Do I need to create a new folder for the project?
ESPEN HOVLANDSDAL: You can do Sanity in it, here, and it'll prompt you to -- where you actually want to place it.
JASON LENGSTORF: Uh-oh. Unauthorized. I need to be logged in.
ESPEN HOVLANDSDAL: Try doing Sanity logout, first.
JASON LENGSTORF: Sanity logout. Okay.
ESPEN HOVLANDSDAL: And do Sanity again.
JASON LENGSTORF: All right. Pretty sure I've done this before. I am now logged in. All right. So, here I go...I'll put it under -- let's create a new project, actually. We're going to call this...something that I'll remember later, "Sanity User Content."
ESPEN HOVLANDSDAL: At this point, we should explain that because there are two modes right now, which are either public data sets or private data sets and public data sets means anyone can query without using any sort of authorization token.
JASON LENGSTORF: Okay.
ESPEN HOVLANDSDAL: So, in this case, it depends on what you want to put in there. But I guess we can start with a private dataset and then -- we want to fetch some data in the frontend maybe, later. Just do the default.
JASON LENGSTORF: Do the default. Creating a dataset. My output path is...hm. If I just type a different folder name, will it be relative?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: Sanity/usercontent. And, can I start clean or do you want me to start with a default?
ESPEN HOVLANDSDAL: I recommend going with a blog because it just has a schema. You have some kind of reference for the different schema types or what the fields look like, so it's just -- you don't have to bounce back and forth to documentation unless you're, like, really familiar with Sanity, in which case, a clean project is a good idea. It's good to have some kind of reference for those schema files.
JASON LENGSTORF: I'm seeing some flaps and some grows in the chat. Remember that you can -- you can run Grow and it will grow my beard and if you grow it enough, there's a little treat that Cassidy built on that stream. You'll have to coordinate, though, because you can only run Grow so many times. So, you got to get in there. Spam that chat. Get in there.
Now I have a Sanity project in. It's going. It's all good. We got success. Now what? I like that. So, I'm going to move into my Sanity content. Sanity Docs -- oh, wow! That's a nice touch. I like that. Good. I'm going to move that over into this browser so I don't lose track of it. What else? We can do Sanity Manage to open the project settings in a browser. We can explore the CLI manual and we can start. So, what would you recommend as the first -- first start?
ESPEN HOVLANDSDAL: Um, so, I guess, we'll need some kind of schemas. Or, figure out what kind of -- what we're building and what the content model should be.
JASON LENGSTORF: Yeah. So, chat, we were thinking, what -- what should we build today? So, what is something that you think would be fun to allow -- so, here's our goal, right, what we want to do is we want to build something today that before this episode is over -- You hackers! You dirty hackers!
JASON LENGSTORF: Yes. So, what --
Where are we? Did we get lost? So, we need an idea. Okay. Guestbook. That's a good idea. Knut, you're seeing the chat. You work at Sanity, you can't tee-up ideas.
Beard care products with reviews. Adopt a party. I like that. Ohhh, how would that work? Hmmm. Hmmmm. If you wanted to adopt a party Corgi. A recipe book would be cool. A recipe book, I love the idea of that. That's actually something I want to build. I'm worried the schema would take too long. Unsplashed photos of Corgis. Name suggestions for Corgis with realistic bios!
I'm into it. So, I like that idea because that'll be fun because what we can do is, like, what if we -- we post a photo. So, we store a photo in Sanity, of a Corgi. And we'll grab photos from Unsplash and then we can allow people to submit suggested names for that Corgi. So, that's kind of a fun idea. And then -- hmmm -- am I getting too ambitious? I'd love to have favorite names and then people could vote on their favorite names?
ESPEN HOVLANDSDAL: I guess we can try.
JASON LENGSTORF: We have the Corgi and we'll come up with suggested names and let people vote on their favorites and if we have extra time, we can allow for write-in submissions of names. Does that seem reasonable?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: All right. Let's do this. I'm very excited about this. I've got my Sanity project open. So, by default, we've got a few folders here. So, it looks like we’ve got Sanity Start. We've got a test. Some default dependencies. The static folder is empty, so we can close that one out. And, do we ever need to go into this Config folder?
ESPEN HOVLANDSDAL: Usually not. It's for plugin conflicts, which in this case, there probably isn't going to be any.
JASON LENGSTORF: Am I in -- okay. Yeah. So, let me create a new project here and I'm actually going to close this and reopen it so -- I think everything was ignored, which is why everything was so dark in there. This is a little better-looking. That'll be easier for us to deal with. Let's get this out of here.
So, this is the folder we're working in, the Schemas folder. What should I do first?
ESPEN HOVLANDSDAL: So, I guess we need to figure out the content model for this thing. So, I guess we can start by modeling the actual Corgi photos. I feel like that's a good place to start.
JASON LENGSTORF: So I'm going to create a file. I have no idea what goes in it. I'm going to create "corgi.js."
ESPEN HOVLANDSDAL: A post or author might be a good start so you can see what the schemas look like. We've got a couple different schema types in Sanity, like the base types. And one of those are the document type. That's, like, the ground-level type. One document represents, like, one entry into the database.
JASON LENGSTORF: Okay. And so I'm going to call this a Corgi. We'll give it a title of "corgi." And this is going to be a document, right? Okay. So, then we need fields. Fields is an array. Is there anything other than fields? It looks like there's a preview --
ESPEN HOVLANDSDAL: That's optional. I guess we can add that later. We probably want to make the photo appear in the previews and stuff.
JASON LENGSTORF: Okay. So, we need -- I think just an image, right?
ESPEN HOVLANDSDAL: Yeah, I mean --
JASON LENGSTORF: Let's think through this.
ESPEN HOVLANDSDAL: Do we just go to an image and have people suggest names?
JASON LENGSTORF: So -- well, and I guess the suggested names would actually be, like, in here. It would be, like...
ESPEN HOVLANDSDAL: Yeah. So, I'm thinking maybe we should -- because what you can do with documents is reference them in other documents. You can create, like, actual relationships between documents and given people want to actually suggest names for -- for Corgis, maybe we can name those as individual documents and reference this Corgi so that it's actually easier to vote than up and down.
JASON LENGSTORF: Makes sense.
ESPEN HOVLANDSDAL: We could store it in an array here, but -- the easiest thing would be to just model it all in here, to be honest, because then you'd have one document per Corgi. And all the names and votes are in there. You might end up with 10,000 names.
JASON LENGSTORF: Yeah, I think if we were to look at this as "what would a production applying look like?" We'd want an upper bound to that. We're probably not going to go any further than that. But I think we can -- we can -- for this -- for this demo, let's say that there are no rules. You can do whatever you want. We can create the Corgi graph. Excellent. Okay. So, if I want to create -- it looks like we've got two -- like, we're not really creating -- let's start with a simple thing, because we -- what I want to show is the easiest thing, which is, like, a string, right? And I think that because we're pulling from Unsplash, we need to create a credit. So, we will include a photo credit and that will be a string and so this -- this is, like, the simplest thing, right? So, if I start -- will Sanity live reload the schemas? If I get this running and open Sanity, we can save and see as it builds? Let's do that.
ESPEN HOVLANDSDAL: We need to open Schema and make sure you import this file and the array.
JASON LENGSTORF: We'll import Corgi from "Corgi." So, down here, I'm going to get rid of all of these -- in fact, I'm just going to delete these and we're just going to put "corgi" in here and I'm going to delete these, as well. This is how you actually tell Sanity what to do. So, if I pull this over, we're bringing in -- I don't exactly know how that -- do you want to talk about what that is?
ESPEN HOVLANDSDAL: It's a little bit magic. Plugins are allowed to define or declare schema types. As Knut says in this thing, the columns here, there's an asset source for Unsplash we might want to use if we want to save some time. That's one example where a plugin can define a schema type and instead of having you explicitly import all the schema types for those plugins, there's a shortcut where you can just say, "import all of the declared schema types." And you'll get an array of those schema types.
JASON LENGSTORF: Nice.
ESPEN HOVLANDSDAL: I kind of forgot about this one. If you want, we can add --
JASON LENGSTORF: I've never used a plugin, so, let's try it. I'm going to go in here and run Sanity install asset source on Unsplash. And, it's doing things. Says it's installed. Good. And then --
ESPEN HOVLANDSDAL: If you open Sanity.JSON, you should see, because you installed it by using Sanity Install, it'll add it to the plugin array there.
JASON LENGSTORF: Now, let's -- all right. So, I've done this. I've got this running. I'm going to start Sanity. And I'm going to do that using, I believe it was -- do I run Sanity Start?
ESPEN HOVLANDSDAL: Yes.
JASON LENGSTORF: Okay.
ESPEN HOVLANDSDAL: So, now -- this is kind of what differentiates Sanity from most headless CMSs. It's running locally as a client-side application. You can, of course, build and deploy it and host it on Netlify or anywhere you want. Just static files. But while you're developing, you're running this local development server. So, you'll see that changes that you do the schemas are reflected once you do changes.
JASON LENGSTORF: Okay. So, this starts with what -- like, this is what we created, right? We've got our photo credit. And this is what I think is really cool, right? So, we created this content type of Corgi. You go in and you get a list of all the Corgis and get a photo credit. If we go in and modify this -- like, let's go in and mess with this. I'm going to create a new type and we'll call this "boop." And it rebuilds. It restores and it's right there. So, this is really, really nice. As a content creator, I can go in and just say, like, "hey, I need a new field on this." I copy/paste a section of my schema and it just immediately shows up and, like, that is really, really nice.
JASON LENGSTORF: Oh, interesting. Look at the "boops" getting stuck up there. I wonder what I did?
I started rebuilding the "boop" thing because I've got a whole project -- it's, like, tools for streamers and I broke that one, I guess. Look it! I totally broke it! How did I even do that? That's -- okay. Hold on. Let's just -- let's just defy gravity for a minute. Let's drop a bunch of these.
I have literally no idea how I did that, but I'm very into it. Okay. So, anyways, we've -- we've rewritten the laws of physics today. Let's write this schema. So, I've got a photo credit. But it looks like we can use the -- the Sanity plugin. So, if I want to use this, what do I do?
ESPEN HOVLANDSDAL: That's a good question. I actually haven't used it before, so it'll be interesting.
JASON LENGSTORF: An asset document, so I don't need that. I actually just use it.
ESPEN HOVLANDSDAL: I guess we might need some kind of API key? I don't know. Or maybe you don't, for this particular one? I'm not familiar with the --
JASON LENGSTORF: I do have an API key. No API key needed. Um -- Unsplash will come as an option for the image type, so we just do a regular image type?
ESPEN HOVLANDSDAL: I guess so, yeah.
JASON LENGSTORF: Okay. Okay. We should probably update that -- those docs a little bit.
ESPEN HOVLANDSDAL: Let's start by adding image fields.
JASON LENGSTORF: So, there's a main image. Let's call this name "corgi image." And then type, image. I don't think -- we can leave the options out, right? So, I've saved, which means that in here, here's our new setup. Let me make this bigger. "Select from Unsplash." That's cool. That's extremely cool. Let's pick a good Corgi. Let's see...I've used this one before. That's a good Corgi. And, did it give me the credit?
ESPEN HOVLANDSDAL: Yeah. I think if you -- if you click on the hamburger menu in the upper-right, and then do Inspect, you might see -- yeah, you'll see that the Corgi image is referenced to an image document and I think that image document has that credits embedded in it, but it's a reference so you can't actually follow it right now.
JASON LENGSTORF: Okay.
ESPEN HOVLANDSDAL: But if you -- if you do -- if you click -- you can actually take a look at the actual document that was inserted into the database.
JASON LENGSTORF: If I click on what now? Vision?
ESPEN HOVLANDSDAL: Vision tool. We can write a query. Maybe this is jumping deep right away.
JASON LENGSTORF: I'm not going to lie, I didn't know that button existed so we're all learning today. Let's do it.
ESPEN HOVLANDSDAL: We can run a query that's our query language that looks like GraphQL. If you do "start," that'll give you any document in the database. Try running that and see what you get. You'll see the first one, here, is the Corgi document that we created. The second one, here, is the actual Sanity assets that holds the credit line and holds metadata. It has an image placeholder.
JASON LENGSTORF: Perfect. Okay. So, I found that. So, that works. And so now if I publish this -- and I wanted to do this, specifically, because I wanted to show what happens when we add fields to published documents. So, now we've got this and then the other thing that we need is we need to have, like, an arbitrary number of suggested names and a vote count. So, if we want to build that, I would need to add a new type. So, I can come up with the name part. So this would be, like, "suggested names," and we'll give it a title of "suggested names." And then I don't know what to do because it's not an array, right?
ESPEN HOVLANDSDAL: It kind of is an array because you just want -- you just want a name and a number of votes. All right. So, it can be an array of objects, I guess.
JASON LENGSTORF: Okay. So, if I do a type array, looks like there's one here. And that says it's an array of...and I don't know what this means. So, let me pull this open a little bit.
ESPEN HOVLANDSDAL: Yeah. So, you can either -- if you're referencing other documents, it's a type reference. In this case, it's an embedded object. So, if we want to do this in the, like, proper way, we make a new schema type for that. If you just want something right away, you can inline the type declaration here. So, off-type object.
JASON LENGSTORF: And do I need the array, always?
ESPEN HOVLANDSDAL: Yeah. I think it'll work without it, but I think it's better because it's easier to expand if you want multiple types.
JASON LENGSTORF: Do I need the name and title?
ESPEN HOVLANDSDAL: Yeah. You need a name.
JASON LENGSTORF: Name would be --
ESPEN HOVLANDSDAL: "Suggested name," I guess? I don't know if you need the title, but...
JASON LENGSTORF: I guess we can see what happens and then do I set fields?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: Okay. And so then the first one is going to be the name, which is the name. Title would be "name." And the type is a string. Okay. And then the next one would be votes. Title is "votes." And the type is -- is it number?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: Okay. All right. So, let's see if that worked. Suggested name. So, I'm going to add a name. And we will call this -- what do you think, chat? What's the right name for this Corgi?
ESPEN HOVLANDSDAL: Maybe we should name it after the person who suggested this whole Corgi thing in the first place. Frank is the suggested name.
JASON LENGSTORF: So, we'll start out with one name. Then we've got "Frank." And "Roger." Okay. So, that's three suggested names. And now, we've got -- like, this is -- you know, this is basically what we're after, right? We've got this photo. We've got a photo credit. And we've got suggested names, with a vote count. And if we go into one of these, we can see the vote counts.
ESPEN HOVLANDSDAL: You don't even need, like, the credits field. It can actually be fetched from the referenced asset so we don't really actually need the photo credit.
JASON LENGSTORF: Ooohhhh! That's a good point! Let's simplify that then. Let's drop that out.
ESPEN HOVLANDSDAL: You'll now see you have a field which is not declared in your schemas. If you go to the bottom, it found an unknown field so you might want to unset that.
JASON LENGSTORF: That's another thing I think is cool. I literally just deleted data and instead of forgetting it exists, Sanity is letting me know, "hey, you just got rid of some data." I could go back and re-add this field if I wanted to bring the data back in.
So, now I have a Corgi and suggested names. So, let's play with previews real quick, because I think that's an interesting thing to do. Can we show the Corgi image and maybe in the suggested names, we could show the votes, as well?
ESPEN HOVLANDSDAL: Sure.
JASON LENGSTORF: So, if I want to do that, I go down to here...and I type in "preview." And then if I look at the bottom, this is an object. And I don't know what to do next.
ESPEN HOVLANDSDAL: So, "select" is the one that chooses sort of which fields you want to select because in a realtime setting and the documents can be really, really large, you don't want to fetch all the data. Let's say you have 1,000 documents in a list view, you don't want to fetch a huge blob of text. You want the ones you're actually going to use. Some of these fields have special meaning. Media is used for automatically figuring that out. You can also provide a prepare function which gets the data you selected and then you can modify that.
JASON LENGSTORF: If I save this, will this just work?
ESPEN HOVLANDSDAL: I think so.
JASON LENGSTORF: Nice! Okay. And if I wanted to -- this might be a little -- maybe we don't want to do this. I would thinking we could pull a credit out and stick it in as untitled and that would be an example of the Prepare function.
ESPEN HOVLANDSDAL: Do "title," and then if you do "corgiimage.asset.credit line," was it?
JASON LENGSTORF: Is that going to just work?
ESPEN HOVLANDSDAL: Might. Hopefully.
JASON LENGSTORF: Holy crap! I was already to make this complicated. So, then the next thing is we've got our fields, here, and so under our fields, we can just add a preview here, as well, right?
ESPEN HOVLANDSDAL: Yeah. This is what I talked about. At some point, you probably want to break this out into a separate schema type so you can have more separate -- more easily-separated. But, yeah.
JASON LENGSTORF: Okay. So, then for the title, I want the name. And then I would -- do I just also get the -- like, the votes like this?
ESPEN HOVLANDSDAL: The number of votes, you can do -- if you just want the number of votes -- you just don't want a number, you probably want a text saying "number of votes" or something. You can do "subtitle votes," but you probably want some kind of --
JASON LENGSTORF: Let's start there and see if we can -- that's super-cool. Okay. If I didn't want to do that, though, I would do "votes" and I would need a "prepare" function?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: I want the whole selection and then I would return selection and subtitle would be "selection votes." No. We would do it like this...right?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: Beautiful. I love it. Like, it's -- it's one of those things -- it's such a minor thing to be able to do that, but to be able to customize what you're, like, heads-up display looks like is such a convenient thing. It makes it so nice. So, let's go -- let me just silence this real quick. This... making noise.
So, we've got "preview setup." So, I think, let's take this thing online. So, let's say, I'm going to publish this and I'm going to add one more -- or, let's not. We're going to do one Corgi and we're going to put this on a website and give people time to vote. We've got 45-ish minutes to make that happen.
I'm going to clean this up a little bit. Let's take these, get rid of them. Let's take this, get rid of that. Okay. So, now we have a -- a standalone schema. We're ready to rock and roll. So, we need to create a website, right?
So, I'm going to -- I'm just going to stop this for now and then I'm going to -- how do you usually do this? Do you put, like, both into the same repo?
ESPEN HOVLANDSDAL: It's totally up to you. I -- I have my opinions and a lot of my teammates have different opinions. I generally don't like to mix frontends and backends but I know a lot of people like to combine it into one thing. I think it's easier, in terms of dependencies, to maintain two different code paths for the frontend and backend.
JASON LENGSTORF: I can see that. That makes sense. Let's do that. I'm going to make a directory called "Sanity User Content Frontend." And we'll move into it. No, not that one. Okay. And then, in here, we will get “init.” "Get branch move." Nope. Here it is "get checkout." We're in a brand-new repo and we can -- how do we want to build this? Let's build it with something simple. Oh! Hold on! I did a whole thing for this. I have a whole, new repo that I'm really excited about. I built a demo, like a demo base, for demos and stuff, that is just -- it's like a template repo, right? And so I'm going to create a new copy and we'll call this "Sanity User Content Frontend." We'll make it public. And I'm going to create a repository from Template.
We're going to remove Frontend and then I'm going to "get clone." Then, we can open this up, as well. All right. So, demo, demo. So, in here, what this basically did is it took -- it just gives me a plain slate where we can write whatever code we want here, add styles and we can add scripts and then we can call this whatever we want. So, let's do "Sanity User-Generated Content." Let's make that into a real sentence and this will be San User Content Frontend. And I'll deal with the rest of that later. So, we can just drop these. Okay. Drop that. All right. So, now from here, we're pretty much set to do whatever we need to do. And, this is all set up to run locally so that we can build whatever we want and if we do something like this...or actually, we'll just do the liquid template.
And then, let's start it. Oh! Ha-ha! I forgot to install my dependencies. Here we go. Here we go.
All right. So, now, we have a running demo. Look at that! It's beautiful.
ESPEN HOVLANDSDAL: Beautiful!
JASON LENGSTORF: So, I'm ready to rock. Let's make a site out of our Corgi image and we're going to do that by -- what? We want to start by probably loading the data, right?
ESPEN HOVLANDSDAL: Yep.
JASON LENGSTORF: Okay.
ESPEN HOVLANDSDAL: So, we got a Sanity Client, I would recommend using it to get started.
JASON LENGSTORF: All right. So, to get that, I'm going to NPM Install Sanity/client?
ESPEN HOVLANDSDAL: Yep.
JASON LENGSTORF: Okay. Do I need anything else?
ESPEN HOVLANDSDAL: No.
JASON LENGSTORF: Okay. And then, is this going to do the fetching or do I need, like, a fetch?
ESPEN HOVLANDSDAL: It'll do it all. It'll do fetch to clients and stuff.
JASON LENGSTORF: Excellent. All right. So I'm assuming we're doing this in a serverless function?
ESPEN HOVLANDSDAL: You can fetch it directly from the frontend if you want.
JASON LENGSTORF: Would that expose keys, though?
ESPEN HOVLANDSDAL: No, because we chose "public" you can do it without any API keys.
JASON LENGSTORF: Even for writing?
ESPEN HOVLANDSDAL: Not for writing. If you want to vote, you can load --
JASON LENGSTORF: I understand. Okay. Yeah. Let's do that. I -- okay. So, is the Sanity Client React-based?
ESPEN HOVLANDSDAL: The client isn't. You can use anything you want there.
JASON LENGSTORF: Okay. I am low-key convinced that this isn't going to work with this demo because I don't know that I can just import any frontend -- I don't know if I can import Node modules. I should probably test that. Let's -- let's do it in a function because I know, for sure, that'll work, and then I'll figure this out for future demos to make sure that I don't break things.
Knut's got us covered. Let's do it. He found data fetching in Sanity. So, we can just pull it straight in. Excellent. All right. So, we're going to create a data folder and inside of it, we're going to create Corgis or Corgis.JS. And inside of that, we don't need block content. But we do need the Sanity Client. Which is looks like is configured elsewhere.
ESPEN HOVLANDSDAL: It's probably just setup with some project ID and datasets, I assume. That's usually how it's done.
JASON LENGSTORF: Okay. Cool. So, we're only going to have the one, so I'm going to set this up in the same project. So, let's get Sanity Client equals require Sanity Client. And then we get the config.
ESPEN HOVLANDSDAL: We can hard-code that now. Do Sanity Client and then...then you need the project ID and the dataset. It's located in the Studio Folder. You'll see the Sanity JSON file.
JASON LENGSTORF: So, Sanity Client and then we need some config and you said, the -- Sanity JSON.
ESPEN HOVLANDSDAL: So the API section there, has -- yep.
JASON LENGSTORF: Okay. So, I need those values.
ESPEN HOVLANDSDAL: Then you can also use CVN true, that'll stop it from complaining because you haven't decided whether or not you should use the CVN.
JASON LENGSTORF: Okay. Use CVN True. I don't have a token --
ESPEN HOVLANDSDAL: You don't need the token right now because you're patching it out for public dataset. If you have private dataset, you want to set up some kind of token to be able to read those things. Those are drafts that you want to read from, then you also need a token. Depends on how you model this.
JASON LENGSTORF: Oh, boy. We --
ESPEN HOVLANDSDAL: This is very -- like, this is big query. You don't really need that whole big query. You need "client.fetch" and give it some query and that'll give you the data you want.
JASON LENGSTORF: I got "fetch" and then the query.
ESPEN HOVLANDSDAL: For fetching all Corgis, that would be "star." So, a string that has a star and square brackets. And then, say, underscore type equals Corgi -- or, double-equals Corgi. And double quotes around "corgi." That will get all queries.
JASON LENGSTORF: Async function, load corgis. And then we can await this. All right. And so, when we await it, it's going to give us back --
ESPEN HOVLANDSDAL: It's going to give you back an array of Corgi documents.
JASON LENGSTORF: So, let's add the cache in case something goes wrong. We're going to "console.error." So, let's return that. Corgis. And then down here, we can say the data that we want is just exporting that. So, module.export. Load Corgis. Okay. And then, in here...we should be able to just get those posts so we can go into the Sanity stuff, just look at the posts. Which are going to be somewhere around here. Probably archived. Set post lists. Collections.mypost. I think what we can probably do, actually, is do, like -- for Corgi in collections, Corgis and then we can just, like, re -- just JSON stringify this. Let's look at Post List. Post List...we just -- we know -- we know what the data looks like, so let's do an image source equals corgi.corgi.asset -- crap, do you know what that one's called?
ESPEN HOVLANDSDAL: "Asset" is a reference, so if we want the entire asset document, we actually have to modify the query a little bit to actually follow that reference. So, if we go -- I thing we need to go in and modify the query for that.
JASON LENGSTORF: Okay. So, let's go into "corgis.JS."
ESPEN HOVLANDSDAL: Now we're stuck with raw Corgi documents and we need to specify which fields we want to fetch and follow any references that we might need. So, if you add curly brackets at the end of that --
JASON LENGSTORF: Like this?
ESPEN HOVLANDSDAL: Yep. And if you can select all the fields of the -- of those documents by doing "..."
JASON LENGSTORF: I'm just going to make this a little easier to track here.
ESPEN HOVLANDSDAL: And then if you do a "comma." And a new line. Then, we're saying we want all the fields and then we want -- for the asset field -- sorry. What was it? Corgi image field. If you do "corgi image" and a new square bracket -- sorry. Curly brackets. And then "..." and then call on -- "comma." And then "asset." And then an arrow, which means it'll follow that reference that's in our single --
JASON LENGSTORF: Like that?
ESPEN HOVLANDSDAL: Yeah. It'll follow the -- materialize the asset reference, so that should work.
JASON LENGSTORF: Okay. So, I'm going to just log this because I am not going to lie, I'm kind of, like, mystified by what just happened. So, let's stringify Corgis Null 2. All right. And then I'm going to come out --
ESPEN HOVLANDSDAL: Inside of the Studio, at some point.
JASON LENGSTORF: Oh, yeah! Let's do that. Let me open another tab and then let's go into -- JSON and it was all user content and Sanity start. All right. And then, we can go in here and just take this query and let's open up the new studio, once this finishes. There it goes. All right. So, that opens here or here? All right. So, I'm going to go back to "vision" and run this query.
ESPEN HOVLANDSDAL: Yeah. So, if you do -- yeah. So, now you'll see that it has -- the Corgi image now, if you expand that asset, it has the entire document. But if you don't do that, if you just remove the asset arrow thing, in the query, you'll see you just get the -- it just has the -- the asset is now just a reference that references that.
And then of course, behind that arrow, you can also choose to do another projection so if you just want the credit line and not, like, all of those fields, which you might not use, that you can do, like, projection and say "credit line."
JASON LENGSTORF: So, I would want the credit line and I would also want this URL, right?
ESPEN HOVLANDSDAL: Yeah. You need a comma after "credit line."
JASON LENGSTORF: Get my asset. Get my credit line and my URL. Okay, that's nice. I like that. And then I also need -- really, in here, really, all I need is the suggested names? So, that simplifies our result, which is nice.
So, I get the image. I get the suggested names. That's great.
ESPEN HOVLANDSDAL: You might want to select the ID of this Corgi just to -- so, it's "_ID" which is the Sanity document ID property.
JASON LENGSTORF: Got it. Okay. Let's -- let's take that back in and run it.
ESPEN HOVLANDSDAL: It says that you might need some -- define some kind of collection. I'm not too familiar with --
JASON LENGSTORF: I think -- I think what will happen with this Corgis document is that it gets -- so, this basically exports all of the Corgi data as a Corgis object, which I think just works in Sanity. I like -- I know enough about Sanity to be dangerous and not enough to be, like, super-knowledgeable. So -- Hopefully I don't get us into a bunch of trouble here. And I'm definitely experimenting in production because this is the first time I've used this demo I made. I really hope it doesn't turn out to be a huge waste of my time. So then what we get back -- if we go look at the Studio -- is we're going to get a result --
ESPEN HOVLANDSDAL: That's not part of it. It'll just be the array.
JASON LENGSTORF: We want "corgi image asset URL." Okay. And then, for the ALT, we'll do "corgi, corgi, image, asset, credit line." And then, we'll ... this. And we'll make sure that it works. So, let me save that and then I'm going to restart "Nullify Dev" over here. It did work. Okay. So, it at least didn't explode, so let's go see if it worked on the demo side. Here. It did not. And it didn't because...I did something incorrect. Um, so, let me go look at -- let me go look at this starter. And let's see what Knut did. I don't want pagination, though. I just want -- I just want the data. And that should be available as Front Matter, which means maybe I can just do it in, like, this? There it goes. Okay.
So, yeah, so, basically what we're doing when we export data like this is we're just putting it right into the -- like, the general Front Matter object that's available to the templates. We've got our Corgi so I can do some basically styling here. We'll set the image to be width, 100%. I think that should solve that problem. Good. Okay.
And then, we can tell it to also include -- we should probably -- Corgi and then we can bump this out, move these over here and underneath it, we want to have the suggested name. So, let's do "suggested names" and we'll do a -- like, we're going to want to change the way this is written, I think, but we can do "name in Corgi suggested names." And then we'll N4. We should be able to do, like, a list item and we'll say that we want name.name and, like, name.votes. Votes. That work? Didn't work. And it probably didn't work because I probably got something wrong.
In our Studio, we got back "suggested names." Name votes. Corgi.suggestednames. Hmmm. Hmmm. Did it -- what if I just, like -- corg.suggestednames. Does that show up? Okay. So that doesn't want to show up at all. Oh! I just screwed up entirely there. There we go. So, that was just a capitalization problem. So, I can get rid of this. Okay.
So, there is our -- our basic listing. We've got the -- the Corgi and then next to the Corgi, we have our -- our suggested names. And so, to make this a little easier to read, maybe -- well, I'm not going to write -- I'm not going to write CSS right now. I'll clean it up, after, and we'll make it look a little bit nicer. We can put the Corgi to the left and the suggested names to the left. But let's put in a "vote" button.
I think what we can do is do something like this, that will be like -- let's see...hmmm. We can say, just a button -- no, we'll call it a link because it's going to -- how would I do this? Eco, are you in the chat? We're going to click a button that's going to fire off a backend request. It is taken you somewhere, so it might be a link?
ESPEN HOVLANDSDAL: Are we going to refresh the page, to refresh the data or how does that work?
JASON LENGSTORF: It could, but I think we'll probably do it asynchronously. It's a button because it performs an action. That's what I thought. Thank you for confirming. "Vote for this name." And we would need to do something like -- we need to add, like, data. Name= -- actually, do I want to include -- do we get a name -- or an ID -- for the suggested names?
ESPEN HOVLANDSDAL: "Underscore+t" property.
JASON LENGSTORF: And that's unique?
ESPEN HOVLANDSDAL: It's unique within that array.
JASON LENGSTORF: So I would still need the data. -- "d" is going to be "corgi.id." So now, if we go and look at this, we have a vote option and then I can go and look in here to see that we've got the entry ID in our data and the key for this particular name in the data, as well. So now what I want to do is I want to actually send -- like, I want to increment this in Sanity when someone clicks this button. So, how would I do that?
ESPEN HOVLANDSDAL: So, the client has a bunch of mutation methods. So, we probably want to create some sort of function, if that's how we were treating this. So, some kind of vote function, I guess.
JASON LENGSTORF: Okay. So, we'll call this "vote for name." And then I'm going to go in -- we're going to duplicate things a little bit here, but I'm just going to copy this across.
ESPEN HOVLANDSDAL: For this thing, you will need a token because you're doing mutations and so, that will require you to --
JASON LENGSTORF: Okay. Let me just set up the -- the function here. Um, so, if it succeeds, it's going to send back -- send back the total votes. Okay. So, I need to get a token. And, I'm going to get that token here or somewhere else?
ESPEN HOVLANDSDAL: You have to open "manage.sanity.io."
JASON LENGSTORF: Okay.
ESPEN HOVLANDSDAL: We'll add this to the CLI at some point.
JASON LENGSTORF: Okay. So, this is also cool because it also automatically created this in the hosted instance, as well. So, then, I'm going to go to "setting."
ESPEN HOVLANDSDAL: And "API."
JASON LENGSTORF: So, we'll have to add a new origin when we deploy. But for now, I'm going to add a new token. It's read-only -- no, it's a "write" token. So we want to be able to write and vote for names. I probably want to pull this off-screen, don't I? Yes. I'm going to add this token. Okay. So, I've now gotten my token and it is in my clipboard and so I need to add an environment variable, which I will do in Netlify.
So, this is one of things that I really like about this. I'm going to Netlify "Open" and that's going to open my Netlify dashboard. It opened in the wrong window. I'll open it over here. So I'm going to go here. We've got our new site. I haven't created the site yet, which is why it isn't working. So, I'm going to Netlify "init." And configure a new site. We want to call this "Sanity User Content." Okay. So, we've created a site. Our build command, we're just going to leave these because Netlify does all the work for us.
Okay. That's all set. So, now -- I'm already here, so I'll just refresh the page. Here's Sanity User Content. I'm going to go into my settings and I've got my build and deploy and environment variables. How nice! Sarah built this cool thing that lets me put in stuff without -- it's an extension for blurring your Netlify stuff. So, I'm going to call this Sanity -- does it need to have a certain name?
ESPEN HOVLANDSDAL: Not really. I guess it depends on your environment.
JASON LENGSTORF: So, I can save that. Cool. That's saved. So, I'm going to close it and then if I rerun this, now, it's pulling in that environment variable locally because I'm logged in as me. So, I don't have to deal with environment variables and setting up a .n file. I can put it on Netlify and I know it's going to work.
That means that here, for our token -- what's this one called?
ESPEN HOVLANDSDAL: Token.
JASON LENGSTORF: I can do process.n.sanityapitoken. So, then, we are now able to do a write operation.
ESPEN HOVLANDSDAL: In this case, you should use CVN False. It doesn't really matter because you're doing mutations. It's kind of confusing to have the --
JASON LENGSTORF: Okay.
ESPEN HOVLANDSDAL: So, um...yeah. You're going to send the Corgi ID and the key, I guess, to the function.
JASON LENGSTORF: Uh-huh. So, we're going to get the -- the Corgi ID and the -- I'm just going to call these "Corgi ID and name key." JSON. Parse. Event.body. Okay. And then we need to send those off. And so, to do that, we're going to do what? Client --
ESPEN HOVLANDSDAL: So, you've got a patch method that takes -- confused that you don't get any sort of --
JASON LENGSTORF: So, I've turned off a lot of the auto-suggest stuff because CVS is so helpful that it gets hard to live-code. Because so many things pop up, it can be a little hard to track what's going on, so I've disabled most of it.
ESPEN HOVLANDSDAL: So the first argument will be the document ID, so Corgi ID.
JASON LENGSTORF: I'm not doing it anymore. I'm doing it secure. You can find the variables. What is it called? It is...let's look at extensions. Chrome Extensions. It is called Netlify Master. And Sarah built that for hiding environment variables. If you need it, get it, it's in the store.
It's not named, the ID is what I'm setting?
ESPEN HOVLANDSDAL: Yeah. And then -- I think that's it for the function. Then that'll return a patch instance. So you'll call methods on this. So, the first thing that you want to do, here -- I guess we need to, then -- so, we need to increment the votes count within the array, that matches where the element matches a certain thing. So, that's a little complicated. We'll have to use... Match to do that. The method is "inc," like "increments." And it'll take an object. The key, here, will be the attribute we want to match. So that will be "suggested names."
JASON LENGSTORF: Oh!
ESPEN HOVLANDSDAL: And then it'll be --
JASON LENGSTORF: Right? No?
ESPEN HOVLANDSDAL: "Suggested names" is an array so we need to find a specific key or element within that -- within that array, right?
JASON LENGSTORF: Right. So, yeah. So, that was kind of what I was -- what I was -- should we go in here and try to write this?
ESPEN HOVLANDSDAL: I don't think we can. But, it's pretty -- it should be straightforward. It should be "suggested names" and then brackets, like "JSON Mutation." Where underscore key -- sorry. I'm pseudocoding in my head. "Underscore key equals equals" and then double quotes and you have to inject the name key here, so you probably want to use one of those --
JASON LENGSTORF: Uh-huh. Like so?
ESPEN HOVLANDSDAL: Yeah. And then one as the -- you want to increment it by one.
JASON LENGSTORF: 0hhhhh! Okay. So, we're basically saying -- well, that's kind of nice that that just does that. Why doesn't it like this? What did I miss? Is it just, like -- what are you unhappy with? There's an object. Let's look at the console. Property assignment expected? That's what I did. Am I missing something obvious?
ESPEN HOVLANDSDAL: Yeah, you need bracket --
JASON LENGSTORF: 0hhhhhh, I got it. That makes sense.
ESPEN HOVLANDSDAL: This will be returning the patch instance, so, in theory, you might add multiple patch operations to this. But since we're done with this now, you can do ".comment" to actually send this off. Commit has properties. You can call it like this and that'll wait for it -- actually, the sort of stored and be available to the search store. That's a good default. And that'll return a promise, so you might want to await that, I guess, before you actually do the response, just in case --
JASON LENGSTORF: Is it going to give me back the updated object?
ESPEN HOVLANDSDAL: It'll give you back the entire document, at this point.
JASON LENGSTORF: The entire document, being an individual Corgi that was updated?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: So if I get my Corgi, I should be able to say "let's get the votes." New votes equals Corgi.suggestednames.find and then we can do the name key equals "name key." And then we can return new votes. Is that right?
ESPEN HOVLANDSDAL: .votes, I guess.
ESPEN HOVLANDSDAL: Oh, yeah.
JASON LENGSTORF: "Name" is going to be -- what was it? "Corgi ID." And the value would be "name key."
ESPEN HOVLANDSDAL: Corgi.id.
JASON LENGSTORF: Oh, yep. Wrong one. That one in here. And then...other one was called "name key." Okay.
So, now, we will be able to handle the "submit" action for these forms. Okay. And, inside of them, we will be able to -- we'll get -- let's see...so, we'll do the "const data equals new form data." "Event.target." And then we can get the Corgi ID and the name key from data -- no, we can't. Not like that. We're, instead, going to do it as "data.getcorgiID." And "data.getnamekey." That means I can also get rid of this. Ah-hah!
All right. So, now, we have the ability to submit these. So I need to then send it off to something so I'm going to use the Fetch API and our result is going to be "await.fetch." And we're going to send it to "Netlify, functions and vote for name." And what we're going to send is a post. We're going to set -- we could set the content type. I'm not going to stress about it because we don't really need to. We can just, you know, send it as JSON. And I'm going to send in the Corgi ID and the name key. Okay. And then, what we get back is going to be a result that we want to get in JSON. And then, if anything goes wrong, we'll catch that.
All right. So, then, from here, we would need to update our thing. And I'm thinking that the way we want to do that is probably to have a span class votes so that we can just update the inner text of that vote. And the thing that we're going to have to figure out is how to get into the -- I guess we can just do it in, like -- yeah, we can do it like this. We'll do the vote node is going to be "event.target.queryselectorvotes." And then, votenode.innertext=result -- let's just change that to, like, 37 and make sure that it works. And I'm going to console-log the result that so we can see what format comes back. And then, we need to document "query selector all" form and then "add event listener for clicks." That is "send vote." I think that'll work.
ESPEN HOVLANDSDAL: Does that work on Collections? I didn't know.
JASON LENGSTORF: Great question. We're about to find out. Nope, it just sent that off for everything. No, wait, I saw something else? Yeah, you can't. I was wrong. So, instead, we'll do "constforms=" and then we need to -- this isn't -- can you just iterate, like, "forms." For each?
ESPEN HOVLANDSDAL: I guess it's an iterable thing.
JASON LENGSTORF: Yeah.
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: Is that going to work? I hope that works. So, let's fire off a vote and let's see what happens. Uncaught. Failed to construct form data, parameter 1 is not of type form element. Yeah, Eco, I'm still going to have to loop because I'm going to have -- like, if there's multiple Corgis, we're going to have multiple forms so it still needs to kind of define the given thing. Yeah, the radio, I think, would be a pain in the butt. So, instead --
ESPEN HOVLANDSDAL: You have a click. It should be a "submit," right?
JASON LENGSTORF: Ohhhhhh! Absolutely! Yes, it definitely should. Okay. So, we got closer. Got a "not found" for "vote for name." Which I think we need to stop and restart because we added that after the function. Let's try it. It did update. So, we're close.
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: Still doesn't like that "vote for name." It doesn't like "vote for name." Functions. Vote for name. Rerunning -- function server is running. But vote for name -- did I typo something?
ESPEN HOVLANDSDAL: Do you have to deploy or anything for them to be able to --
JASON LENGSTORF: Oh! I opened the wrong server, is why. Okay. So, then we get "totalvotes=1." That is dope! I'm so excited about this. Now we have, here, we can set this to "result, total votes." Yes. Okay.
Uhhhh, it's working! Okay. So, now, if I refresh...it must be -- eleventy only runs the first time. How would we need to do that?
ESPEN HOVLANDSDAL: Just for -- can you go into the "vote for name" thing? If you change the -- on the commit, if you add an object to the commit, which says "visibility async."
JASON LENGSTORF: Like that?
ESPEN HOVLANDSDAL: Yeah. And save that. It should be returning a little faster, so it should be less sluggish on the --
JASON LENGSTORF: Oh, nice. That's nice and fast.
Okay. So, then the other thing that we would need is, like, a client-side query for the first time that we load, to get the latest vote count. So --
ESPEN HOVLANDSDAL: You could also set up a listener, as was suggested in the comments here. You could theoretically set up a listener and reflect that in realtime.
JASON LENGSTORF: Yes. Okay. So, it sounds like we probably want to do a follow-up to dig further into this. But because we're basically out of time, what I'm going to do is I'm going to deploy this. So, let's get "add all." And then let's check what's in here. We've got our "vote for name." We've got our package. We've got the Corgis data and then the demo. Let's hit "commit." And say, "mostly-working demo." Okay. So I'm going to push this. All right. And then, we're going to Netlify "open." I think this is going -- oh, no, it opened the right browser. Okay. I need to add this to Sanity.
So, in my --
ESPEN HOVLANDSDAL: Actually, you're not fetching -- because you're not fetching data from the client side, you don't actually need this.
JASON LENGSTORF: Oh, for real? 0hhhhh! Nice! Okay. So, that means, chat, in -- now, basically, go hit this website and let's -- so, pulled in our votes. Let's -- there we go. So, now we can see that people are voting, that aren't me, and so whenever I -- so, a lot of love for Frank in here. Jeez, Frank, everybody's in for Frank.
In the desk, if I go into this Corgi, we should see these update in realtime, right?
ESPEN HOVLANDSDAL: Yeah.
JASON LENGSTORF: So, keep voting, chat. Keep going.
ESPEN HOVLANDSDAL: Or not.
JASON LENGSTORF: Maybe it's not updating in realtime? Chris Corgi is way out into the lead as we wrap-up the episode. Espen -- So, actually Chris is joking. He brings up a good point in the chat. If you were trying to do this with user input where you needed some kind of a control, you would want to look into something like access control. You would want to have somebody log in and then store their user ID, made this vote and then they can't vote more than once. You could do IP filtering. You would want to make it more difficult to sit there and mash that button like I've been doing and I assume most people have been doing. So, there are a lot of ways that you would take this further. Oohhhh! Frank coming from behind, to a huge lead! Okay. I think maybe we got to close it. This is Frank now. So, Espen, where should people go if they want to learn more?
ESPEN HOVLANDSDAL: Definitely go to Sanity.io and check it out. We have a very active community for Slack. If you want to talk to us and talk to other community members, that's a great way. If you want do ask me any questions, follow me on Twitter.
JASON LENGSTORF: Chat, stay tuned. We've got some really fun stuff coming up this week. I'm especially excited for Thursday, because on Thursday, I've got Nathaniel coming back to the show. He's great. But what I'm really excite about is we're going to build a text-to-vote game. You're going to able to participate in polls that I'm in tending to show live on the screen so if we want to have a game, we'll be able to text votes and track things throughout the show and we'll going to build that with Twili0.
Next week, we have Charlie coming back. These robots, they're inline. We sat them down and had a talk.
We have Christian coming on and we're going to talk about Hasura and talk about GraphQL. This is going to a really fun couple weeks of content. Make sure you subscribe to the show. If you want to get updates, we have a calendar. That auto-updates.
Thank you, again, to our sponsors, Netlify, Fauna, Sanity. Captioning is handled by White Coat Captioning.
Espen, thank you so, so much for hanging out today. This was a whole lot of fun and I'm looking forward to taking this even further sometime down the road.
Chat, as always, thanks for hanging out. Stay tuned. We're going to raid. Espen, thank you.
We will see you next time.